我正在使用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
但是我无法按照我想要的方式工作。
答案 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这样的帮助程序库,而不是编写自己的throttle
或debounce
,并将您的函数传递给API以获取函数的限制版本:
使用jQuery的示例,但也应该使用d3:
// Avoid excessively updating the position while scrolling.
jQuery(window).on('scroll', _.throttle(updatePosition, 100));
答案 3 :(得分:0)
你不需要一大段代码或像D3js这样的超大库来获得一个像样的油门功能。节流功能的目的是减少浏览器资源,而不是应用你正在使用的更多开销。此外,我对油门功能的不同用途需要它们有许多不同的情况。这是我列出的一个“好”油门功能需要的东西。
而且,我相信以下节流功能可以满足所有这些。
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
的值。