【问题标题】:raphael: how to start "drag" event on object via code instead of via "mouse down"?raphael:如何通过代码而不是通过“鼠标按下”在对象上启动“拖动”事件?
【发布时间】:2014-04-28 14:53:30
【问题描述】:

我需要能够在单击鼠标左键时创建的圆形对象上启动 raphael“拖动”事件。

详情:
我正在研究一段操纵封闭路径的代码。我需要这些功能:
1. 通过鼠标拖动移动现有点
2. 右键删除点
3.在路径上左键添加点,导致路径在该位置分裂
4. 如果点是通过左键单击创建的,允许用户在释放鼠标之前将其拖动到另一个位置

这是我的函数,它在给定的 raphael“路径”对象上创建一个新圆圈:

// create a new circle wrapped around the given point reference
function make_circle(point, path) {
    var c = paper.circle(point[1], point[2], 6)
                 .attr({fill: "#DDD", stroke: "black"});

    // record the point and path reference in the circle to allow updates
    c.data("point", point).data("path", path);

    // set event handlers
    c.drag(dragmove,dragstart);
    c.update = update_coordinates_circle;
    c.mousemove(handle_mousemove_circle);
    c.mouseup(handle_mouseup_circle);
    c.mousedown(handle_mousedown_circle);

    return c;
}

那么我可以这样做:

var pt1 = ["M", 0, 0],
    pt2 = ["L", 10, 0],
    pt3 = ["L", 10, 10],
    pt4 = ["L", 0, 10],
    point_set = [pt1, pt2, pt3, pt4, ["Z"]],
    path = paper.path(point_set),
    c1 = make_circle(pt1, path),
    c2 = make_circle(pt2, path),
    c3 = make_circle(pt3, path),
    c4 = make_circle(pt4, path);  

通过点击路径创建新点时,我这样做:

make_circle(new_point, this).data("just_created", true);  

... 这个圈子的 mousemove 处理程序检查:

if (this.data("just_created")) { ... // follow mouse  

我的完整代码在这里http://jsfiddle.net/T7XS3/

问题在于,因为圆的半径很小,所以在释放新点之前快速移动鼠标会破坏 mousemove 处理程序,因为它已附加到圆上。

通过“拖动”事件移动现有圆圈时,一切正常。无论鼠标移动多快,圆圈都会与它保持一致。

同样,是否可以在按下鼠标左键时创建但尚未释放的对象上启动 raphael“拖动”事件?


解决方案 (http://jsfiddle.net/A27NZ/3/):

var width = 300,
    height = 300,
    paper_offset = 50,
    maxX = width,
    maxY = height,
    paper = Raphael(paper_offset, paper_offset, 300, 300),
    dragging;

make_path([100, 100], [200, 100], [200, 200], [100, 200], "green");

// reset 'dragging' to avoid initial drag
dragging = null;


// some math to determine if a point is between two other points, within some threshold
// based on: http://stackoverflow.com/questions/328107/how-can-you-determine-a-point-is-between-two-other-points-on-a-line-segment
function isBetween(a, b, c) {
    var x1 = a[1],
        x2 = b[1],
        x3 = c[1],
        y1 = a[2],
        y2 = b[2],
        y3 = c[2],
        THRESHOLD = 1000;

    var dotproduct = (x3 - x1) * (x2 - x1) + (y3 - y1) * (y2 - y1);
    if (dotproduct < 0) return false; // early return if possible

    var squaredlengthba = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
    if (dotproduct > squaredlengthba) return false; // early return if possible

    var crossproduct = (y3 - y1) * (x2 - x1) - (x3 - x1) * (y2 - y1);
    if (Math.abs(crossproduct) <= THRESHOLD) return true;
    else return false;
}


function global_mousemove(e) {
    if (dragging) {
        handle_mousemove_circle.call(dragging, e);
    }
}


function global_mouseup(e) {
    dragging = null;
}


if (document.addEventListener) {
    document.addEventListener("mousemove", global_mousemove, false);
    document.addEventListener("mouseup", global_mouseup, false);
} else {
    document.attachEvent('onmousemove', global_mousemove);
    document.attachEvent('onmouseup', global_mouseup);
}


// move circle to given coordinates
function update_circle_xy(new_x, new_y) {
    var point = this.data("point"),
        path = this.data("path");

    // don't follow the mouse outside the canvas, we don't want to lose the point
    if (new_x <= 0) {
        new_x = 0;
    } else if (new_x >= maxX) {
        new_x = maxX;
    }

    if (new_y < 0) {
        new_y = 0;
    } else if (new_y > maxY) {
        new_y = maxY;
    }

    // update circle coords
    this.attr({
        cx: new_x,
        cy: new_y
    });

    // update the referenced point
    point[1] = new_x;
    point[2] = new_y;

    // redraw the path
    path.attr({
        path: path.data("point_set")
    });
}


// move circle based on mouse event
function handle_mousemove_circle(e) {
    var new_x = e.clientX - paper_offset,
        new_y = e.clientY - paper_offset;

    update_circle_xy.call(this, new_x, new_y);
}


// handle mouse down on circle
// e.which 1 = left click
// e.which 3 = right click
function handle_mousedown_circle(e) {
    // remove the target point on right-click
    if (e.which === 3) {
        var path = this.data("path"),
            point_set = path.data("point_set"),
            point = this.data("point"),
            index = point_set.indexOf(point);

        // don't do anything if we only have 2 points left
        // (checking if < 4 because last element is not a point ("Z"))
        if (point_set.length < 4) return false;

        // remove the target point
        point_set.splice(index, 1);

        // if removed point was head of set, make the following point the new head
        if (index === 0) point_set[0][0] = "M";

        // redraw the path
        path.attr({
            path: point_set
        });

        // finally, remove the circle
        this.remove();
    } else if (e.which === 1) {
        dragging = this;
    }
}


// handle mouse click on path
function handle_mousedown_path(e) {

    // split on left-click
    if (e.which === 1) {
        var X = e.clientX - paper_offset,
            Y = e.clientY - paper_offset,
            new_point = ["L", X, Y],
            point_set = this.data("point_set"),
            index;

        // "open" the path by removing the end ("Z")
        point_set.pop();

        for (var i = 0; i < point_set.length; i += 1) {
            // cur point
            var pt1 = point_set[i], // cur point
                pt2; // next point

            // circular wrap for next point
            if (i === point_set.length - 1) {
                pt2 = point_set[0];
            } else {
                pt2 = point_set[i + 1];
            }

            // check if these are the two points we want to split between
            if (isBetween(pt1, pt2, new_point)) {
                index = i + 1;
                break;
            }
        }

        // we should have found a place to insert the point, put it there
        if (index) {
            point_set.splice(index, 0, new_point);
        } else {
            return; // we didn't find a place to put the new point
        }

        // "close" the path with a ("Z")
        point_set.push("Z");

        // redraw the path
        this.attr({
            path: point_set
        });

        // create new circle to represent the new point
        c = make_circle(new_point, this);
    }
}


// create a new circle wrapped around the given point reference
function make_circle(point, path) {
    var c = paper.circle(point[1], point[2], 6).attr({
        fill: "#DDD",
        stroke: "black"
    });

    // record the point and path reference in the circle to allow updates
    c.data("point", point).data("path", path);

    // set event handlers
    c.mousedown(handle_mousedown_circle);

    // start dragging the new circle
    dragging = c;
    return c;
}


// create a new colored path from four point coordinate pairs
function make_path(p1, p2, p3, p4, color) {

    // starting points
    var pt1 = ["M", p1[0], p1[1]],
        pt2 = ["L", p2[0], p2[1]],
        pt3 = ["L", p3[0], p3[1]],
        pt4 = ["L", p4[0], p4[1]],
        point_set = [pt1, pt2, pt3, pt4, ["Z"]],
        path = paper.path(point_set).attr({
            stroke: color,
                "stroke-width": 5,
                "stroke-linecap": "round"
        });

    // keep a reference to the set of points
    path.data("point_set", point_set);

    // add listener to the path to allow path-splitting
    path.mousedown(handle_mousedown_path);

    // create the circles that represent the points
    make_circle(pt1, path);
    make_circle(pt2, path);
    make_circle(pt3, path);
    make_circle(pt4, path);
}

【问题讨论】:

    标签: javascript raphael


    【解决方案1】:

    我原以为您可以在此答案“Raphaël Object: Simulate click”中使用该技术的一些变体来将事件传递给圆圈,但这不起作用。

    我的另一个想法是基于 Raphael 源代码的工作原理,当您使元素可拖动时,会为 mousedown 事件添加处理程序。 应该可以在正确的上下文中直接调用该处理函数并将您已经拥有的 mousedown 事件传递给它(在handle_mousedown_path 内)。但它非常hacky,我无法让它工作,以防其他人可以这样做:

    c = make_circle(new_point, this).data("just_created", true);
    c.events[0].f.call(c, e); // This is very specific to this scenario
    

    我能想到的唯一其他方法是将 mousemove/mouseup 处理程序添加到整个文档中。所以创建一个全局变量dragging,当你创建一个圆集dragging作为元素时,在全局mouseup处理程序中你可以清除变量:

    function global_mouseup(e) {      
        dragging = null;
    }
    

    您已经有一个移动处理程序,所以使用它,我们只需要确保this 对函数是正确的(参见这个答案:Javascript: how to set "this" variable easily?

    function global_mousemove(e) {     
        if (dragging) { 
            handle_mousemove_circle.call(dragging, e);    
        }
    }
    

    现在,将它们绑定到文档(并删除圆圈本身的各个处理程序):

    if (document.addEventListener) {
        document.addEventListener("mousemove", global_mousemove, false);
        document.addEventListener("mouseup", global_mouseup, false);
    } else {
        document.attachEvent('onmousemove', global_mousemove);
        document.attachEvent('onmouseup', global_mouseup);
    }  
    

    (请参阅此答案:Add event handler to HTML element using javascript 了解我们使用该语法的原因,尽管如果您使用的是框架,则可以使用它的命令)。

    然后,working fiddle

    它不是很完美,因为 Raphael 拖动处理程序更智能并且知道将对象保留在画布内,您需要更改 handle_mousemove_circle 函数来解决这个问题。

    【讨论】:

    • +1 这与我的想法一致。感谢您提供一个工作示例,我更新了 handle_mousemove_circle() 以更好地工作并且更兼容 IE 和 FF jsfiddle.net/A27NZ/2
    • 好东西,感谢分享最终的工作代码,希望其他人在未来发现它有用。
    • 再次更新:jsfiddle.net/A27NZ/3。重构和改进以包含来自 Raphael's demo 的一些想法,这些想法似乎可以很好地进行边界检测。我想我终于满意了(现在......)
    【解决方案2】:

    SpaceDog 提供的解决方案对我来说非常有效。但是有一个小问题:如果您通过单击路径连续添加两个新圆圈,而不是将新圆圈拖动到某处,则每秒“拖动”是错误的,因为路径上的 mousedown 事件会触发标准拖动功能html元素(see this fiddle by mikhail)

    为了防止这种行为,只需在文档中添加另一个 EventListener,如果使用“自定义”拖动功能,它会检查每次 mousedown。如果是这样,则阻止默认行为:

    if (document.addEventListener) {
        document.addEventListener("mousemove", global_mousemove, false);
        document.addEventListener("mouseup", global_mouseup, false);
        document.addEventListener("mousedown", global_mousedown, false);
    } else {
        document.attachEvent('onmousemove', global_mousemove);
        document.attachEvent('onmouseup', global_mouseup);
        document.attachEvent('onmousedown', global_mousedown);
    }
    
    function global_mousedown(e) {  
        if(dragging){
            e.preventDefault();     
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-27
      相关资源
      最近更新 更多