如何使用D3.js限制鼠标事件的函数调用

时间:2017-04-14 08:28:11

标签: javascript d3.js

我正在使用D3.js的.on()调用DOM元素的“mousemove”事件上的函数,如下所示:

d3.select("#myelement").on("mousemove", myfunc);
function myfunc(){
    // Just to show that I need to get the mouse coordinates here
    console.log(d3.mouse(this));
}

我需要我正在调用的函数来了解事件,即鼠标坐标。

由于我的其余代码在计算上非常昂贵,我想节制调用myfunc,比如每200毫秒。

如何在this中保留myfunc的价值(以便d3.mouse(this)仍有效)? 我试过这个去抖功能:https://davidwalsh.name/javascript-debounce-function 还有:https://remysharp.com/2010/07/21/throttling-function-calls 但是我无法按照我想要的方式工作。

4 个答案:

答案 0 :(得分:5)

问题是没有将this传递给debounce函数,这很容易,正如你在JSFiddle中看到的那样(我正在链接一个JSFiddle,因为Stack片段在登录时会冻结{{ 1}}或D3选择)。

真正的问题是传递D3事件:因为this在事件结束后为空,所以你必须保持对它的引用。否则,您在尝试使用d3.event时会出现Cannot read property 'sourceEvent' of null错误。

因此,使用second link的功能,我们可以对其进行修改以保留对D3事件的引用:

d3.mouse()

这是演示,将鼠标悬停在大圆圈上,慢慢移动鼠标:

function debounce(fn, delay) {
    var timer = null;
    return function() {
        var context = this,
            args = arguments,
            evt = d3.event;
            //we get the D3 event here
        clearTimeout(timer);
        timer = setTimeout(function() {
            d3.event = evt;
            //and use the reference here
            fn.apply(context, args);
        }, delay);
    };
}
var circle = d3.select("circle");

circle.on("mousemove", debounce(function() {
  console.log(d3.mouse(this));
}, 250));

function debounce(fn, delay) {
  var timer = null;
  return function() {
    var context = this,
      args = arguments,
      evt = d3.event;
    clearTimeout(timer);
    timer = setTimeout(function() {
    	d3.event = evt;
      fn.apply(context, args);
    }, delay);
  };
}
.as-console-wrapper { max-height: 30% !important;}

PS:在JSFiddle和Stack片段中,仅当停止移动鼠标时才会调用该函数,这不是<script src="https://d3js.org/d3.v4.js"></script> <svg> <circle cx="120" cy="80" r="50" fill="teal"></circle> </svg>所需的行为。我会继续努力。

答案 1 :(得分:2)

感谢Gerardo Furtado的回答,我设法通过调整this page的节流功能来解决我的问题:

function throttle(fn, threshhold, scope) {
  threshhold || (threshhold = 250);
  var last,
      deferTimer;
  return function () {
    var context = scope || this;

    var now = +new Date,
        args = arguments,
        event = d3.event;
    if (last && now < last + threshhold) {
      // hold on to it
      clearTimeout(deferTimer);
      deferTimer = setTimeout(function () {
        last = now;
          d3.event = event;
        fn.apply(context, args);
      }, threshhold);
    } else {
      last = now;
        d3.event = event;
      fn.apply(context, args);
    }
  };
}
});

现在回调知道d3.event和d3.mouse(this)可以在函数内正常使用。

答案 2 :(得分:0)

编辑:我不确定它是否会在您的案例中保留this关键字,但您可以试一试。

您可以简单地使用像lodash这样的帮助程序库,而不是编写自己的throttledebounce,并将您的函数传递给API以获取函数的限制版本:

使用jQuery的示例,但也应该使用d3:

// Avoid excessively updating the position while scrolling.
jQuery(window).on('scroll', _.throttle(updatePosition, 100));

https://lodash.com/docs/#throttle

答案 3 :(得分:0)

需要一大段代码或像D3js这样的超大库来获得一个像样的油门功能。节流功能的目的是减少浏览器资源,而不是应用你正在使用的更多开销。此外,我对油门功能的不同用途需要它们有许多不同的情况。这是我列出的一个“好”油门功能需要的东西。

  • 最小开销。
  • 自上次通话以来,如果已超过 interval MS,则立即进行函数调用。
  • 避免执行另一个间隔 MS的执行功能。
  • 延迟过多的事件触发,而不是完全放弃事件。
  • 在需要时更新延迟事件,使其不会变得“陈旧”。
  • 当限制功能延迟时,阻止事件的默认操作。
  • 能够删除节流事件侦听器侦听器。

而且,我相信以下节流功能可以满足所有这些。

var cachedThrottleFuncs = [],
    minimumInterval = 200; // minimum interval between throttled function calls
function throttle(func, obj, evt) {
    var timeouttype = 0,
        curFunc;
    function lowerTimeoutType(f){
        timeouttype=0;
        if (curFunc !== undefined){
            curFunc();
            curFunc = undefined;
        }
    };
    return cachedThrottleFuncs[ ~(
        ~cachedThrottleFuncs.indexOf(func) || 
        ~(
          cachedThrottleFuncs.push(function(Evt) {
            switch (timeouttype){
                case 0: // Execute immediatly
                    ++timeouttype;
                    func.call(Evt.target, Evt);
                    setTimeout(lowerTimeoutType, minimumInterval);
                    break;
                case 1: // Delayed execute
                    curFunc = func.bind(Evt.target, Evt);
                    Evt.preventDefault();
            }
          }) - 1
        )
    )];
};
function listen(obj, evt, func){
    obj.addEventListener(evt, throttle(func, obj, evt));
};
function mute(obj, evt, func){
    obj.removeEventListener(evt, throttle(func, obj, evt));
}

使用示例:

listen(document.body, 'scroll', function whenbodyscrolls(){
    if (document.body.scrollTop > 400)
        mute(document.body, 'scroll', whenbodyscrolls();
    else
        console.log('Body scrolled!')
});

或者,如果您只需要添加事件侦听器,并且不需要删除事件侦听器,那么您可以使用以下更简单的版本。

var minimumInterval = 200; // minimum interval between throttled function calls
function throttle(func, obj, evt) {
    var timeouttype = 0,
        curEvt = null;
    function lowerTimeoutType(f){
        timeouttype=0;
        if (curEvt !== null){
            func(curEvt);
            curEvt = null;
        }
    };
    return function(Evt) {
        switch (timeouttype){
            case 0: // Execute immediately
                ++timeouttype; // increase the timeouttype
                func(Evt);
                // Now, make it so that the timeouttype resets later
                setTimeout(lowerTimeoutType, minimumInterval);
                break;
            case 1: // Delayed execute
                // make it so that when timeouttype expires, your function
                // is called with the freshest event
                curEvt = Evt;
                Evt.preventDefault();
        }
    };
};

默认情况下,此功能限制为每200ms最多一次调用。要将间隔更改为不同的毫秒数,只需更改minimumInterval的值。