我正在尝试使用D3从Mike Bostock重新创建this可拖动地球,但作为svg版本。由于拖动时的性能问题,我正在重新渲染地球。到目前一切尚好。现在,我想实现一个单击事件,但它不起作用。Here提到问题可能是重新添加。mousedown事件工作正常,但会干扰稍后的拖动。为什么mousedown事件起作用而click事件不起作用?对于重构代码以解决此问题的提示,我们非常感谢。
为了更好地理解,我创建了一个提琴:Fiddle
PS。我是编程和D3的新手,所以请不要太苛刻:)
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3</title>
</head>
<body>
<div id="world"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://unpkg.com/topojson-client@2"></script>
</body>
</html>
JS:
let width, height
height = 150
width = 150
const projection = d3.geoOrthographic()
.scale((height - 10) / 2)
.translate([100, height / 2])
.precision(0);
let path = d3.geoPath().projection(projection)
const svg = d3.select("#world")
.append("svg")
const g = svg.append("g")
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json").then(data => {
let data1 = data
renderGlobe(data1);
})
function renderGlobe(world){
g.call(drag(projection)
.on("drag.render", ()=>render(world, true))
.on("end.render", ()=>render(world, false) ))
.call( () => render(world, false))
}
function render(world, x){
if(x){
variable = "land"
world = topojson.feature(world, world.objects.land).features;
}
else{
variable = "countries"
world = topojson.feature(world, world.objects.countries).features;
}
g.selectAll("path").remove()
g.selectAll(`${variable}`)
.data(world)
.enter().append("path")
.attr("class", `${variable}`)
.attr("d", path)
// This click event doesn't work
.on("click",()=>console.log("Do something"))
// But mousedown event works
.on("mousedown",()=>console.log("Mousedown event works"))
}
function drag(projection){
var LonLatStart, eulerStart
function dragstarted(event){
LonLatStart = projection.invert(d3.pointer(event))
eulerStart = projection.rotate()
}
var LonLatEnd, eulerEnd
function dragged(event){
LonLatEnd = projection.rotate(eulerStart).invert(d3.pointer(event))
eulerEnd = getEulerAngles(LonLatStart, eulerStart, LonLatEnd)
projection.rotate(eulerEnd)
refresh()
}
return drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
}
function refresh(){
svg.selectAll("path").attr("d", path)
}
// Dragging Math
let cos = Math.cos,
acos = Math.acos,
sin = Math.sin,
asin = Math.asin,
atan2 = Math.atan2,
sqrt = Math.sqrt,
min = Math.min,
max = Math.max,
PI = Math.PI,
radians = PI / 180,
degrees = 180 / PI;
// a: original vector, b: ending vector
function crossProduct(a, b){
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
]
}
function dotProduct(a, b){
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
function LengthOfVector(c){
return sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2])
}
function quaternionEulerFormula(a, b){
let rotationAxis = crossProduct(a,b) , normalizationFactor = sqrt(dotProduct(rotationAxis,rotationAxis))
if (!normalizationFactor) return [1, 0, 0, 0]
let theta = acos(max(-1, min(1, dotProduct(a, b))))
return [
cos(theta / 2),
sin(theta / 2) * rotationAxis[2] / normalizationFactor,
- sin(theta / 2) * rotationAxis[1] / normalizationFactor,
sin(theta / 2) * rotationAxis[0] / normalizationFactor
]
}
// returns unit quaternion from euler angles [λ, φ, γ]
function unitQuaternion(d){
var lambda = d[0] / 2 * radians, cosLambda = cos(lambda), sinLambda = sin(lambda),
phi = d[1] / 2 * radians, cosPhi = cos(phi), sinPhi = sin(phi),
gamma = d[2] / 2 * radians, cosGamma = cos(gamma), sinGamma = sin(gamma)
return [
cosLambda * cosPhi * cosGamma + sinLambda * sinPhi * sinGamma,
sinLambda * cosPhi * cosGamma - cosLambda * sinPhi * sinGamma,
cosLambda * sinPhi * cosGamma + sinLambda * cosPhi * sinGamma,
cosLambda * cosPhi * sinGamma - sinLambda * sinPhi * cosGamma,
]
}
// quaternion multiplication, returns another quaternion which represents the rotation
function quaternionMultiplication(q0 , q1){
return [
q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] - q0[3] * q1[3],
q0[0] * q1[1] + q0[1] * q1[0] + q0[2] * q1[3] - q0[3] * q1[2],
q0[0] * q1[2] - q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1],
q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0]
]
}
// converts quaternion to euler angles
function quaternion2eulerAngles(q){
return [
atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
//asin(2 * (q[0] * q[2] - q[3] * q[1])) * degrees,
asin(max(-1, min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
]
}
// converts long, lat to cartesian coordinates x,y,z
function lonlat2cartesian(e) {
let l = e[0] * radians, p = e[1] * radians, cp = cos(p);
return [cp * cos(l), cp * sin(l), sin(p)];
};
function getEulerAngles(positionLonLatStart, eulerAnglesStart, positionLonLatEnd){
let v0 = lonlat2cartesian(positionLonLatStart)
let v1 = lonlat2cartesian(positionLonLatEnd)
let quaternionEnd = quaternionMultiplication(unitQuaternion(eulerAnglesStart), quaternionEulerFormula(v0,v1))
return quaternion2eulerAngles(quaternionEnd)
}
发布于 2021-01-03 01:51:19
你永远不会像现在这样触发点击,你已经看到了。这是因为单击既涉及鼠标按下又涉及鼠标释放。它们与拖动行为交互,每个都分别触发开始和结束事件。
在您的例子中,mousedown触发了您添加到路径中的拖动和mousedown侦听器。然后在鼠标释放时,首先触发拖动事件侦听器,删除所有路径和关联的侦听器。render函数会在事件发生后添加新的路径,但是太晚了,无法注册该事件。
有许多解决方案,但可能最简单的方法是删除已有的拖动结束侦听器,并仅在实际发生拖动时才替换它(不是在start事件中,而是在drag事件中):
function renderGlobe(world){
g.call(drag(projection)
.on("drag.render", function(event) {
render(world, true)
event.on("end.render",()=>render(world,false))
}))
.call( () => render(world, false))
}
event.on()
方法允许侦听器仅应用于当前手势。只有在mousedown和mouseup之间有鼠标移动时,拖动监听器才会触发,所以如果只有一次简单的单击,则不会使用这个end监听器。
这是一个分叉的fiddle。
对于这个问题,有相当多的替代解决方案,这里的一个,虽然简单,但可能对点击过程中的移动有点敏感。Here是区分鼠标相关事件(单击和拖动结束)的另一种方法的可能基础。
https://stackoverflow.com/questions/65545439
复制相似问题