YES/NO - 有没有办法改进纯 SVG 工具的鼠标拖动?
YES/NO - is there a way to improve mouse dragging with pure SVG tools?
所以我花了一些时间玩纯(没有外部库)SVG 元素拖动。
总的来说一切正常,但快速移动的鼠标存在这个令人讨厌的问题:
- 当用户鼠标按下靠近其边缘的可拖动 SVG 元素时
- 然后拖动(mousemove)这样的可拖动太快
- 鼠标 "loses" 可拖动
这里更详细地描述了这个问题:
http://www.svgopen.org/2005/papers/AdvancedMouseEventModelForSVG-1/index.html#S3.2
同样在这里,作者试图通过利用 mouseout 事件来修复用户体验:
http://nuclearprojects.com/blog/svg-click-and-drag-object-with-mouse-code/
我在这里复制了上面的代码片段:http://codepen.io/cmer41k/pen/zNGwpa
我的问题是:
有没有其他方法(由纯 SVG 提供)来防止鼠标移动太快时出现这种 "loss" SVG 元素?
我试图解决这个问题是:
- 检测(以某种方式)mouseout 事件发生而没有完成拖动。
- 如果是这样(我们检测到 "disconnect")- 将 SVG 元素重新连接到当前鼠标位置。
为什么这不起作用?
代码:
var click=false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX=0, moveY=0; // keeps track of overall transformation
var lastMoveX=0, lastMoveY=0; // stores previous transformation (move)
function mouseDown(evt){
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click=true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill","green");
}
function move(evt){
evt.preventDefault();
if(click){
moveX = lastMoveX + ( evt.clientX – clickX );
moveY = lastMoveY + ( evt.clientY – clickY );
evt.target.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt){
click=false;
lastMoveX = moveX;
lastMoveY = moveY;
evt.target.setAttribute("fill","gray");
}
缺少代码中最重要的部分,即您如何或更具体地在哪个元素上注册事件。
要防止此问题,您基本上要做的是在最外层的 svg 元素上注册 mousemove 和 mouseup 事件,而不是在要拖动的元素上注册。
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
开始拖动时,在 svg 元素上注册事件,完成后取消注册。
svg.removeEventListener("mousemove", move)
svg.removeListener("mouseup", endMove)
您必须存储当前拖动的元素,以便在其他事件处理程序中可用。
我另外做的是将拖动的 pointer-events 设置为 "none"
元素,以便您可以对拖动元素下方的鼠标事件做出反应(f.e。找到放置目标...)
evt.target.setAttribute("pointer-events", "none")
但不要忘记在拖动完成后将其设置回合理的值
evt.target.setAttribute("pointer-events", "all")
var click = false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX = 0,
moveY = 0; // keeps track of overall transformation
var lastMoveX = 0,
lastMoveY = 0; // stores previous transformation (move)
var currentTarget = null
function mouseDown(evt) {
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click = true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill", "green");
// register move events on outermost SVG Element
currentTarget = evt.target
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
evt.target.setAttribute("pointer-events", "none")
}
function move(evt) {
evt.preventDefault();
if (click) {
moveX = lastMoveX + (evt.clientX - clickX);
moveY = lastMoveY + (evt.clientY - clickY);
currentTarget.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt) {
click = false;
lastMoveX = moveX;
lastMoveY = moveY;
currentTarget.setAttribute("fill", "gray");
svg.removeEventListener("mousemove", move)
svg.removeEventListener("mouseup", endMove)
currentTarget.setAttribute("pointer-events", "all")
}
<svg id="svg" width="800" height="600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle id="mycirc" cx="60" cy="60" r="22" onmousedown="mouseDown(evt)" />
</svg>
更高级
这段代码还有两点不太好。
- 它不适用于 viewBoxed SVG 或内部元素
转换 parents.
- 所有全局变量都是糟糕的编码习惯。
以下是解决这些问题的方法:
编号1 通过使用 getScreenCTM(CTM = 当前变换矩阵)的逆函数将鼠标坐标转换为本地坐标来解决。
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
对于nr。 2 看这个实现:
var dre = document.querySelectorAll(".draggable")
for (var i = 0; i < dre.length; i++) {
var o = new Draggable(dre[i])
}
function Draggable(elem) {
this.target = elem
this.clickPoint = this.target.ownerSVGElement.createSVGPoint()
this.lastMove = this.target.ownerSVGElement.createSVGPoint()
this.currentMove = this.target.ownerSVGElement.createSVGPoint()
this.target.addEventListener("mousedown", this)
this.handleEvent = function(evt) {
evt.preventDefault()
this.clickPoint = globalToLocalCoords(evt.clientX, evt.clientY)
this.target.classList.add("dragged")
this.target.setAttribute("pointer-events", "none")
this.target.ownerSVGElement.addEventListener("mousemove", this.move)
this.target.ownerSVGElement.addEventListener("mouseup", this.endMove)
}
this.move = function(evt) {
var p = globalToLocalCoords(evt.clientX, evt.clientY)
this.currentMove.x = this.lastMove.x + (p.x - this.clickPoint.x)
this.currentMove.y = this.lastMove.y + (p.y - this.clickPoint.y)
this.target.setAttribute("transform", "translate(" + this.currentMove.x + "," + this.currentMove.y + ")")
}.bind(this)
this.endMove = function(evt) {
this.lastMove.x = this.currentMove.x
this.lastMove.y = this.currentMove.y
this.target.classList.remove("dragged")
this.target.setAttribute("pointer-events", "all")
this.target.ownerSVGElement.removeEventListener("mousemove", this.move)
this.target.ownerSVGElement.removeEventListener("mouseup", this.endMove)
}.bind(this)
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
}
.dragged {
fill-opacity: 0.5;
stroke-width: 0.5px;
stroke: black;
stroke-dasharray: 1 1;
}
.draggable{cursor:move}
<svg id="svg" viewBox="0 0 800 600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle class="draggable" id="mycirc" cx="60" cy="60" r="22" fill="blue" />
<g transform="rotate(45,175,75)">
<rect class="draggable" id="mycirc" x="160" y="60" width="30" height="30" fill="green" />
</g>
<g transform="translate(200 200) scale(2 2)">
<g class="draggable">
<circle cx="0" cy="0" r="30" fill="yellow"/>
<text text-anchor="middle" x="0" y="0" fill="red">I'm draggable</text>
</g>
</g>
</svg>
<div id="out"></div>
所以我花了一些时间玩纯(没有外部库)SVG 元素拖动。
总的来说一切正常,但快速移动的鼠标存在这个令人讨厌的问题: - 当用户鼠标按下靠近其边缘的可拖动 SVG 元素时 - 然后拖动(mousemove)这样的可拖动太快 - 鼠标 "loses" 可拖动
这里更详细地描述了这个问题: http://www.svgopen.org/2005/papers/AdvancedMouseEventModelForSVG-1/index.html#S3.2 同样在这里,作者试图通过利用 mouseout 事件来修复用户体验: http://nuclearprojects.com/blog/svg-click-and-drag-object-with-mouse-code/
我在这里复制了上面的代码片段:http://codepen.io/cmer41k/pen/zNGwpa
我的问题是:
有没有其他方法(由纯 SVG 提供)来防止鼠标移动太快时出现这种 "loss" SVG 元素?
我试图解决这个问题是: - 检测(以某种方式)mouseout 事件发生而没有完成拖动。 - 如果是这样(我们检测到 "disconnect")- 将 SVG 元素重新连接到当前鼠标位置。
为什么这不起作用?
代码:
var click=false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX=0, moveY=0; // keeps track of overall transformation
var lastMoveX=0, lastMoveY=0; // stores previous transformation (move)
function mouseDown(evt){
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click=true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill","green");
}
function move(evt){
evt.preventDefault();
if(click){
moveX = lastMoveX + ( evt.clientX – clickX );
moveY = lastMoveY + ( evt.clientY – clickY );
evt.target.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt){
click=false;
lastMoveX = moveX;
lastMoveY = moveY;
evt.target.setAttribute("fill","gray");
}
缺少代码中最重要的部分,即您如何或更具体地在哪个元素上注册事件。
要防止此问题,您基本上要做的是在最外层的 svg 元素上注册 mousemove 和 mouseup 事件,而不是在要拖动的元素上注册。
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
开始拖动时,在 svg 元素上注册事件,完成后取消注册。
svg.removeEventListener("mousemove", move)
svg.removeListener("mouseup", endMove)
您必须存储当前拖动的元素,以便在其他事件处理程序中可用。
我另外做的是将拖动的 pointer-events 设置为 "none" 元素,以便您可以对拖动元素下方的鼠标事件做出反应(f.e。找到放置目标...)
evt.target.setAttribute("pointer-events", "none")
但不要忘记在拖动完成后将其设置回合理的值
evt.target.setAttribute("pointer-events", "all")
var click = false; // flag to indicate when shape has been clicked
var clickX, clickY; // stores cursor location upon first click
var moveX = 0,
moveY = 0; // keeps track of overall transformation
var lastMoveX = 0,
lastMoveY = 0; // stores previous transformation (move)
var currentTarget = null
function mouseDown(evt) {
evt.preventDefault(); // Needed for Firefox to allow dragging correctly
click = true;
clickX = evt.clientX;
clickY = evt.clientY;
evt.target.setAttribute("fill", "green");
// register move events on outermost SVG Element
currentTarget = evt.target
svg.addEventListener("mousemove", move)
svg.addEventListener("mouseup", endMove)
evt.target.setAttribute("pointer-events", "none")
}
function move(evt) {
evt.preventDefault();
if (click) {
moveX = lastMoveX + (evt.clientX - clickX);
moveY = lastMoveY + (evt.clientY - clickY);
currentTarget.setAttribute("transform", "translate(" + moveX + "," + moveY + ")");
}
}
function endMove(evt) {
click = false;
lastMoveX = moveX;
lastMoveY = moveY;
currentTarget.setAttribute("fill", "gray");
svg.removeEventListener("mousemove", move)
svg.removeEventListener("mouseup", endMove)
currentTarget.setAttribute("pointer-events", "all")
}
<svg id="svg" width="800" height="600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle id="mycirc" cx="60" cy="60" r="22" onmousedown="mouseDown(evt)" />
</svg>
更高级
这段代码还有两点不太好。
- 它不适用于 viewBoxed SVG 或内部元素 转换 parents.
- 所有全局变量都是糟糕的编码习惯。
以下是解决这些问题的方法: 编号1 通过使用 getScreenCTM(CTM = 当前变换矩阵)的逆函数将鼠标坐标转换为本地坐标来解决。
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
对于nr。 2 看这个实现:
var dre = document.querySelectorAll(".draggable")
for (var i = 0; i < dre.length; i++) {
var o = new Draggable(dre[i])
}
function Draggable(elem) {
this.target = elem
this.clickPoint = this.target.ownerSVGElement.createSVGPoint()
this.lastMove = this.target.ownerSVGElement.createSVGPoint()
this.currentMove = this.target.ownerSVGElement.createSVGPoint()
this.target.addEventListener("mousedown", this)
this.handleEvent = function(evt) {
evt.preventDefault()
this.clickPoint = globalToLocalCoords(evt.clientX, evt.clientY)
this.target.classList.add("dragged")
this.target.setAttribute("pointer-events", "none")
this.target.ownerSVGElement.addEventListener("mousemove", this.move)
this.target.ownerSVGElement.addEventListener("mouseup", this.endMove)
}
this.move = function(evt) {
var p = globalToLocalCoords(evt.clientX, evt.clientY)
this.currentMove.x = this.lastMove.x + (p.x - this.clickPoint.x)
this.currentMove.y = this.lastMove.y + (p.y - this.clickPoint.y)
this.target.setAttribute("transform", "translate(" + this.currentMove.x + "," + this.currentMove.y + ")")
}.bind(this)
this.endMove = function(evt) {
this.lastMove.x = this.currentMove.x
this.lastMove.y = this.currentMove.y
this.target.classList.remove("dragged")
this.target.setAttribute("pointer-events", "all")
this.target.ownerSVGElement.removeEventListener("mousemove", this.move)
this.target.ownerSVGElement.removeEventListener("mouseup", this.endMove)
}.bind(this)
function globalToLocalCoords(x, y) {
var p = elem.ownerSVGElement.createSVGPoint()
var m = elem.parentNode.getScreenCTM()
p.x = x
p.y = y
return p.matrixTransform(m.inverse())
}
}
.dragged {
fill-opacity: 0.5;
stroke-width: 0.5px;
stroke: black;
stroke-dasharray: 1 1;
}
.draggable{cursor:move}
<svg id="svg" viewBox="0 0 800 600" style="border: 1px solid black; background: #E0FFFF;">
<rect x="0" y="0" width="800" height="600" fill="none" pointer-events="all" />
<circle class="draggable" id="mycirc" cx="60" cy="60" r="22" fill="blue" />
<g transform="rotate(45,175,75)">
<rect class="draggable" id="mycirc" x="160" y="60" width="30" height="30" fill="green" />
</g>
<g transform="translate(200 200) scale(2 2)">
<g class="draggable">
<circle cx="0" cy="0" r="30" fill="yellow"/>
<text text-anchor="middle" x="0" y="0" fill="red">I'm draggable</text>
</g>
</g>
</svg>
<div id="out"></div>