在d3中处理onclick的正常方法是
selection.append(element)
.on("click", someFunction)
如果我在1000 svg元素上这样做,是否意味着我只附加了1000个不同的听众。如果是这种情况,是否有专门针对d3的事件委托?
答案 0 :(得分:6)
@AlexW answer(部分)正确:D3中没有事件委托,只有事件绑定。
但是,我说部分因为最好说“在D3”中没有用于事件委派的原生方法,因为实际上它很容易实现它:使用D3进行事件委托的丑陋的替代方法包括使用d3.event.target。
例如,在这个非常简单的演示中,我们绑定了这些数据......
var data = ["foo", "bar", "baz"];
...到<g>
元素内的圈子。然后,我们将一个事件监听器绑定到该组,并在单击时获取每个圆的基准:
g.on("click", function() {
console.log(d3.select(d3.event.target).datum())
})
就是这样:
var svg = d3.select("svg");
var g = svg.append("g");
var data = ["foo", "bar", "baz"];
var circles = g.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cy", 40)
.attr("cx", function(_, i) {
return 50 + 100 * i
})
.attr("r", 20)
.attr("fill", "teal");
g.on("click", function() {
console.log(d3.select(d3.event.target).datum())
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
这种方法的好处在于,就像jQuery事件委托一样,它适用于在定义了监听器之后创建的元素。在以下演示中,红色圆圈:
var svg = d3.select("svg");
var g = svg.append("g");
var data = ["foo", "bar", "baz"];
var circles = g.selectAll(null)
.data(data)
.enter()
.append("circle")
.attr("cy", 40)
.attr("cx", function(_, i) {
return 50 + 75 * i
})
.attr("r", 20)
.attr("fill", "teal");
g.on("click", function() {
console.log(d3.select(d3.event.target).datum())
});
g.append("circle")
.attr("cy", 40)
.attr("cx", 275)
.attr("r", 20)
.attr("fill", "firebrick")
.datum("foobar")
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
因此,尽管D3没有本地的,明确的事件委派方法,但解决方案非常简单明了。
答案 1 :(得分:3)
是的,这会添加1000个事件监听器:
为指定的每个选定元素添加或删除侦听器 事件类型名称。
https://github.com/d3/d3-selection/blob/master/README.md#selection_on
如果你有1000多个元素,你可能不想使用SVG,因为DOM很容易陷入这么多元素的困境。使用画布等可能更有效。
D3不进行事件委托,只进行事件绑定。因此,如果您仍在考虑使用SVG,则可能需要实施委派using jQuery or vanilla JS。
答案 2 :(得分:0)
这是d3事件委托的实现:
/**
* For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree.
*
* @param { String } selector A string containing a selector expression to match elements against.
*
* @return { Array } D3 selection
* */
d3.selection.prototype.closest = function(selector) {
let closestMatchDom = undefined
let matchArr = []
this.each(function() {
let currentDom = this
while(typeof currentDom.parentNode.matches === 'function' && !closestMatchDom) { // from itself
if(currentDom.matches(selector)) {
closestMatchDom = currentDom
matchArr.push(closestMatchDom)
}
currentDom = currentDom.parentNode
}
closestMatchDom = undefined
})
return d3.selectAll(matchArr)
}
/**
* DIY D3 Event Delegation
*
* @param { String } parentSelector
* @param { String } childSelector
* @param { String } events Such as 'click contextmenu'
* @param { PlainObject } data A custom data which is passed to handler.
* @param { Function } handler A function to execute when the event of childDom is triggered.
* @param { Function } inverseHandler A function to execute when the event of childDom is not triggered but the event of parentDom is triggered
*
* */
function eventDelegate(parentSelector, childSelector, events, data, handler, inverseHandler) {
let $container = d3.select(parentSelector)
$container.on(events, function () {
let event = d3.event
let target = event.target
let $target = d3.select(target)
let $currentTarget = null
if (!$target.closest(childSelector).empty() && !$container.select(childSelector).empty()) {
$currentTarget = $target.closest(childSelector)
} else {
if (inverseHandler) {
inverseHandler.call(target, $target, data)
}
return
}
d3.event.data = data
handler.call($currentTarget.node(), $currentTarget, data)
})
}