Skip to content

Snap之Event

何波 edited this page Jul 11, 2019 · 3 revisions

events

mouse.js为Element.prototype添加event方法,同时为Element.node添加addEventListener和removeEventListener,并挂载到Snap全局变量上。

click, dblclick, mousedown, mousemove, mouseout, mouseover, mouseup, touchstart, touchmove, touchend, touchcancel

var elproto = Element.prototype,
events = [
    "click", "dblclick", "mousedown", "mousemove", "mouseout",
    "mouseover", "mouseup", "touchstart", "touchmove", "touchend",
    "touchcancel"
],
touchMap = {
    mousedown: "touchstart",
    mousemove: "touchmove",
    mouseup: "touchend"
},

addEvent = function (obj, type, fn, element) {
    console.info(element.node === obj) // true
    // 这里的obj一般为element.node,也就是element.node.addEventListener
    // obj也有可能是document
    
    var realName = supportsTouch && touchMap[type] ? touchMap[type] : type,
        f = function (e) {
            var scrollY = getScroll("y", element),
                scrollX = getScroll("x", element);
            if (supportsTouch && touchMap[has](type)) {
                for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
                    if (e.targetTouches[i].target == obj || obj.contains(e.targetTouches[i].target)) {
                        // 如果是touch事件,则让e = e.targetTouches[i],以便与click等事件在回调中的e保持一致
                        var olde = e;
                        e = e.targetTouches[i];
                        e.originalEvent = olde; // 把真的e放到originalEvent中,createjs也是这样干的
                        e.preventDefault = preventTouch;
                        e.stopPropagation = stopTouch;
                        break;
                    }
                }
            }
            var x = e.clientX + scrollX,
                y = e.clientY + scrollY;
            
            // 注意这里的回调参数为e, x, y,包括了点击的坐标
            /*
            let svg = Snap("#svg");
            var c = svg.paper.circle(50, 50, 40).attr({
                fill: "#f00"
            }).click((e, x, y) => {
                console.info(e, x, y)
            })
            */
            return fn.call(element, e, x, y);
        };

    // 真正绑定事件的是element.node,而非element。可以在chrome开发者工具
    // 查看元素所绑定的事件
    if (type !== realName) {
        obj.addEventListener(type, f, false);
    }

    obj.addEventListener(realName, f, false);

    // let unbind = addEvent(...)
    // unbind() 如果执行的话,直接调下面函数内容
    return function () {
        // 清除绑定的事件
        if (type !== realName) {
            obj.removeEventListener(type, f, false);
        }

        obj.removeEventListener(realName, f, false);
        return true;
    };
}

for (var i = events.length; i--;) {
    (function (eventName) {
        // Snap.mousemove,Snap.mouseup也可以执行,如下面的drag方法中会用到
        Snap[eventName] = elproto[eventName] = function (fn, scope) {
            // 如果fn是函数就进行事件绑定,如:Element.click(() => alert('click'))
            if (Snap.is(fn, "function")) {
                // Element.events = []
                // 如果是Snap.mousemove,Snap.mouseup,则this为Snap全局变量
                this.events = this.events || [];
                
                // unbind:addEvent为this.node绑定事件
                // 如果是Snap.mousemove,Snap.mouseup这样的,this.node不存在,则会把事件绑定在document上面
                this.events.push({
                    name: eventName,
                    f: fn,
                    unbind: addEvent(this.node || document, eventName, fn, scope || this)
                });
            } else {
                // 如果 fn未定义,或不是函数,就直接取出Element.events[]数组,执行其中的f方法
                
                // 如:Element.click()
                for (var i = 0, ii = this.events.length; i < ii; i++) if (this.events[i].name == eventName) {
                    try {
                        this.events[i].f.call(this);
                    } catch (e) {}
                }
            }
            return this;
        };
        
        // 添加un bind事件
        Snap["un" + eventName] =
        elproto["un" + eventName] = function (fn) {
            // 从events数组中删除该事件
            var events = this.events || [],
                l = events.length;
            while (l--) if (events[l].name == eventName &&
                           (events[l].f == fn || !fn)) {
                events[l].unbind(); // 同时对真的Element.node进行事件删除
                events.splice(l, 1);
                !events.length && delete this.events;
                return this;
            }
            return this;
        };
    })(events[i]);
}

示例:

let svg = Snap("#svg");
let c = svg.paper.circle(50, 50, 40).attr({
        fill: "#f00"
    }).click((e, x, y) => {
        console.info(e, x, y)
    });
console.dir(c)

// c.click() // 成功
// c.node.click() 会报错
c.node.dispatchEvent(new Event('click')) // 成功

c.unclick() // 清除click事件

输出:

Element:{
    anims: {},
    events: [{name: "click", f: ƒ, unbind: ƒ}],
    id: "circleSjxy35tcy1",
    node: circle,
    paper: Element, 
    type: "circle"    
}

为Element.node绑定的click事件,会放到Element.events[]数组中进行事件记录。 其作用是,如果执行c.click(),则会取出Element.events[]数组中取出name为click的对象,然后执行其中的f()方法,进行事件调用。

注意:执行c.click()是直接进行了函数调用,而非c.node.addEventListener后,执行c.node.click()会报错,这是因为:

let div = document.getElementById('div')

div.addEventListener('click',function(){
  alert('click')
})

div.click() // 执行成功
div.dispatchEvent(new Event('click')); // 执行成功

我们对html元素绑定事件,可以直接执行div.click(),然而svg元素不行

// <svg id="svg" width="2000" height="1000"><desc>Created with Snap</desc><defs></defs><circle id="circle" cx="50" cy="50" r="40" fill="#ff0000" style=""></circle></svg>

let circle = document.getElementById('circle')

circle.addEventListener('click',function(){
  alert('click')
})

circle.click() // 会报错

circle.dispatchEvent(new Event('click')) // 替代

目前Snap.js唯一依赖于eve.js

Element.drag()

  • onmove (function) handler for moving
  • onstart (function) handler for drag start
  • onend (function) handler for drag end
  • mcontext (object) #optional context for moving handler
  • scontext (object) #optional context for drag start handler
  • econtext (object) #optional context for drag end handler

开始事件和句柄调用特定的上下文,或者元素自身带有如下参数的上下文:

  • x 数值。鼠标x位置。
  • y 数值。鼠标y位置。
  • event 对象。DOM事件对象。

移动事件和句柄调用特定的上下文,或者元素自身带有如下参数的上下文:

  • dx 数值。距离鼠标开始点的x偏移。
  • dx 数值。距离鼠标开始点的y偏移。
  • x 数值。鼠标x位置。
  • y 数值。鼠标y位置。
  • event 对象。DOM事件对象。

结束事件和句柄调用特定的上下文,或者元素自身带有如下参数的上下文:

  • event 对象。DOM事件对象。

Adds event handlers for an element's drag gesture

var drag = [],
dragMove = function (e) {
    var x = e.clientX,
        y = e.clientY,
        scrollY = getScroll("y"),
        scrollX = getScroll("x"),
        dragi,
        j = drag.length;
    while (j--) {
        dragi = drag[j];
        if (supportsTouch) {
            var i = e.touches && e.touches.length,
                touch;
            while (i--) {
                touch = e.touches[i];
                if (touch.identifier == dragi.el._drag.id || dragi.el.node.contains(touch.target)) {
                    x = touch.clientX;
                    y = touch.clientY;
                    (e.originalEvent ? e.originalEvent : e).preventDefault();
                    break;
                }
            }
        } else {
            e.preventDefault();
        }
        var node = dragi.el.node,
            o,
            next = node.nextSibling,
            parent = node.parentNode,
            display = node.style.display;
        // glob.win.opera && parent.removeChild(node);
        // node.style.display = "none";
        // o = dragi.el.paper.getElementByPoint(x, y);
        // node.style.display = display;
        // glob.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
        // o && eve("snap.drag.over." + dragi.el.id, dragi.el, o);
        x += scrollX;
        y += scrollY;
        eve("snap.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
    }
},
dragUp = function (e) {
    Snap.unmousemove(dragMove).unmouseup(dragUp);
    var i = drag.length,
        dragi;
    while (i--) {
        dragi = drag[i];
        dragi.el._drag = {};
        eve("snap.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
        eve.off("snap.drag.*." + dragi.el.id);
    }
    drag = [];
};

// 添加Element.prototype.drag()方法
elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
    var el = this;
    // drag无参数,添加默认onmove,onstart事件
    if (!arguments.length) {
        var origTransform;
        return el.drag(function (dx, dy) {
            // onmove 后对当前element添加transform
            this.attr({
                transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
            });
        }, function () {
            // onstart后 origTransform为当前element.transform().local
            origTransform = this.transform().local;
        });
    }
    
    // 如果drag有参数如:
    /*
    c.drag(()=> {
        console.info(333)
    })
    */
    function start(e, x, y) {
        (e.originalEvent || e).preventDefault();
        el._drag.x = x;
        el._drag.y = y;
        el._drag.id = e.identifier;
        // 注意这里是对document绑定mousemove和mouseup事件,而不是当前的Element
        // 执行上面的dragMove和dragUp方法
        !drag.length && Snap.mousemove(dragMove).mouseup(dragUp);
        
        drag.push({el: el, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
        
        onstart && eve.on("snap.drag.start." + el.id, onstart);
        onmove && eve.on("snap.drag.move." + el.id, onmove);
        onend && eve.on("snap.drag.end." + el.id, onend);
        
        eve("snap.drag.start." + el.id, start_scope || move_scope || el, x, y, e);
    }
    function init(e, x, y) {
        eve("snap.draginit." + el.id, el, e, x, y);
    }
    eve.on("snap.draginit." + el.id, start);
    el._drag = {};
    draggable.push({el: el, start: start, init: init});
    // 为当前el只添加mousedown事件,对document添加了mousemove和mouseup事件
    el.mousedown(init);
    return el;
};

// 添加Element.prototype.undrag()方法
elproto.undrag = function () {
    var i = draggable.length;
    while (i--) if (draggable[i].el == this) {
        this.unmousedown(draggable[i].init);
        draggable.splice(i, 1);
        eve.unbind("snap.drag.*." + this.id);
        eve.unbind("snap.draginit." + this.id);
    }
    !draggable.length && Snap.unmousemove(dragMove).unmouseup(dragUp);
    return this;
};

示例1:

let svg = Snap("#svg");
let c1 = svg.paper.circle(50, 50, 40).drag();

示例2:

let svg = Snap("#svg");
let path = svg.paper.path('M30 75 Q240 30, 300 120').attr({stroke:'green','stroke-width': 10, fill:'none'})

let origTransform

// 参照了源码
function onMove(dx, dy) {
    this.attr({
        transform: origTransform + (origTransform ? "T" : "t") + [dx, dy]
    })
    
    // 做其它事情
    console.info(dx, dy)
}

function onStart () {
    origTransform = this.transform().local;
}

// 如果参数存在,则按照源码再重写onMove和onStart函数
path1.drag(onMove, onStart)

Element.hover(f_in, f_out, scope_in, scope_out)

  • f_in 函数。hover进入的事件句柄。
  • f_out 函数。hover出来的事件句柄。
  • icontext 函数。hover进入事件句柄的上下文。
  • ocontext 函数。hover出来事件句柄的上下文。
// 同时为当前element绑定mouseover和mouseout事件
elproto.hover = function (f_in, f_out, scope_in, scope_out) {
    return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
};

elproto.unhover = function (f_in, f_out) {
    return this.unmouseover(f_in).unmouseout(f_out);
};

示例:

let svg = Snap("#svg");
let c = svg.paper.circle(50, 50, 40).attr({
    fill: "#f00"
}).hover(function() {
    // 移入
    this.animate({
        fill: "#00f"
    }, 1000);
}, function() {
    // 移出
    this.animate({
        fill: "#f00"
    }, 1000);
});

Clone this wiki locally