zoukankan      html  css  js  c++  java
  • js仿QQ拖拽删除

    原生js实现仿QQ拖拽删除交互,无需任何依赖。

    项目演示请看这里

    gitHub请移步这里

    由于源码很长,所以贴到最下面了。

    效果截图如下:

    核心思想呢,就是点击圆点的时候全屏覆盖个canvas,在canvas上画出想要的效果。

    原理:

    1.点击圆点,生成全屏canvas,画出两个圆 , 在原地画圆,根据拉动距离变小, 在手指触控的位置,跟随手指画圆。

    2.计算两圆位置,计算两圆的切点位置,通过两圆切断位置画贝塞尔曲线填充,模拟粘性。

    3.判断距离是否超出最大距离,做出对应动作。

    看似简单,但是做起来会有各种问题,

    1. 移动端时, 对小球发生的touchstart事件,该事件对应的touchmove对象只能是该球,所以就会出现,touchmove时的事件处理。

    2. 当touchmove的时候,我们的意愿是不滑动页面,只拖动小球,这就涉及到touchmove的事件阻止问题, 由于第一点原因,将touchmove事件指向了最外层元素,但是此时,如果是给 <body>的话,我们是没法阻止页面滚动的,详情参见google对此事件的说明。

    3. 如何回收使用过的canvas,这里我们是每次声明一个canvas的时候都会给一个随机ID,来通过这个随机id回收,这样既保证了id不会重名,又保证了回收的顺利进行。

    4. 当手指离开的时候,播放小球销毁动画,该动画会有持续时间,如果该动画在全屏canvas上做的话,就会在touchend后的 0.8s (销毁动画的持续时间) 内对屏幕操作是无效的,被canvas阻止, 所以此刻我们选择在销毁处,单独生成个小点的canvas(具体多大取决于你想让销毁动画蔓延多大)来执行销毁动画。

    5. 画贝塞尔曲线填充,仿粘性 , 这里需要计算两个圆的共4个切点,通过四个切点,及中心点,来话二次贝塞尔曲线填充, 这4个点的计算,需要数学方面的知识,原理如下图所示 (图片来源于网络,懒得画了,就这个意思反正):

    剩下的就是函数的封装及canvas的使用了,如有疑问可以看看源码,并不复杂, 如有写的不对的地方欢迎批评指正。

    <div id="body">
        <div class="drag">
            <div class="div1 dragdom" data-target="1">1</div>
        </div>
        <div class="drag">
            <div class="div1 dragdom" data-target="2">36</div>
        </div>
        <div class="drag">
            <div class="div1 dragdom" data-target="3">7</div>
        </div>
        <div class="drag">
            <div class="div1 dragdom" data-target="4">15</div>
        </div>
        <div class="drag">
            <div class="div1 dragdom" data-target="5">9</div>
        </div>
        <div class="drag">
            <div class="div1 dragdom" data-target="6">14</div>
        </div>
    </div>
    #body{overflow: scroll;height: 100vh;}
    .drag{position: relative;padding:10px 30px;z-index: 1;}
    .drag div{
        width: 40px;
        height: 40px;
        color: #FFF;
        text-align: center;
        font-size: 20px;
        line-height: 40px;
        border-radius: 50%;
        background-color: red;
    }
    .dragdom{
        position: relative;
    }
    CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
        // x: 起点X坐标, y: 起点Y坐标  w: 宽度, h: 高度: r: 圆角弧度,  
        if (w < 2 * r) {r = w / 2;}
        if (h < 2 * r){ r = h / 2;}
        this.beginPath();
        this.moveTo(x+r, y);
        this.arcTo(x+w, y, x+w, y+h, r);
        this.arcTo(x+w, y+h, x, y+h, r);
        this.arcTo(x, y+h, x, y, r);
        this.arcTo(x, y, x+w, y, r);
        this.closePath();
        return this;
    }
    function Drag (params) {
        // {
        //    dragId: dragId
        //    max: max,
        //    fillColor: ''
        //}
        var D = this;
        this.drag = params.drag;
        this.max = params.max || 70;
        this.texts = this.drag.innerHTML;
        this.domBody = document.getElementById('body');
        this.device = /android|iphone|ipad|ipod|webos|iemobile|opear mini|linux/i.test(navigator.userAgent.toLowerCase());
        
        this.eventName = {
            start: this.device ? 'touchstart' : 'mousedown',
            move: this.device ? 'touchmove' : 'mousemove',
            end: this.device ? 'touchend' : 'mouseup',
        }
        
        this.onDragStart = function () {
            this.drag.style.visibility = 'hidden'
        }
        this.draged = false;
        this.onDragEnd = function (d) {
            if(!d) {
                D.drag.style.visibility = 'visible'
            }
        }
        this.onBeforeDelate = function () {}
        this.onDelated = function () {}
        this._r = D.drag.offsetWidth / 2;
        this.point = {
            r: D._r,
            x: D.offset(D.drag).left + D._r,
            y: D.offset(D.drag).top + D._r,
            w: D.drag.offsetWidth,
            h: D.drag.offsetHeight
        }
        this.current = { 
            x: 0,
            y: 0,
            r: D.point.r,
            direction: 0,
            canDelate: false,
            fillColor: 'red',
            coefficient: 0.3
        }
        this.fullCanvas = {
            canvas: '',
            ctx: '',
             document.documentElement.clientWidth || document.documentElement.clientWidth,
            height: document.documentElement.clientHeight || document.documentElement.clientHeight,
            id: ''
        }
        
        this.startEvent = function (){
            var e = event;
            D.point.x = D.offset(D.drag).left + D._r;
            D.point.y = D.offset(D.drag).top + D._r ;
            console.log(D.offset(D.drag).top)
            var width = D.fullCanvas.width;
            var height = D.fullCanvas.height;
            var cssObj = {
                'position': 'fixed',
                'left': '0',
                'top': '0',
                'zIndex': '99'
            }
            var convasObj = D.createCanvas(width, height, cssObj);
            D.domBody.appendChild(convasObj.canvas)
            D.fullCanvas.canvas = document.getElementById(convasObj.id);
            D.fullCanvas.ctx = D.fullCanvas.canvas.getContext('2d');
            if(D.device) {
                D.domBody.addEventListener(D.eventName.move, D.moveEvent)
                D.drag.addEventListener(D.eventName.end, D.endEvent)
            } else {
                D.fullCanvas.canvas.addEventListener(D.eventName.move, D.moveEvent)
                D.fullCanvas.canvas.addEventListener(D.eventName.end, D.endEvent)
            }
        }
        this.moveEvent = function () {
            var e = event;
            e.preventDefault();
            D.current.x = D.device ? e.touches[0].clientX : e.clientX;
            D.current.y = D.device ? e.touches[0].clientY : e.clientY;
            D.currentDirection = D.drawCercle(D.fullCanvas.ctx, D.fullCanvas.width, D.fullCanvas.height, D.current.fillColor, D.point.x, D.point.y, D.point.r, D.current.x, D.current.y, D.current.r, D.max)
            if(!D.draged) {
                D.draged = true;
                D.onDragStart(D.drag)
            }
        }
        this.endEvent = function (e) {
            console.log(e.target)
            if(D.device) {
                D.drag.removeEventListener(D.eventName.move, D.moveEvent);
                D.domBody.removeEventListener(D.eventName.move, D.moveEvent)
            } else {
                D.fullCanvas.canvas.removeEventListener(D.eventName.move, D.moveEvent);
            }
            D.draged = false;
            if(D.currentDirection > D.max) {
                isDelate = true;
                D.current.canDelate = false;
                D.disappear({
                    x: D.current.x,
                    y: D.current.y,
                    r: D.current.r
                });
                D.domBody.removeChild(D.fullCanvas.canvas);
            } else {
                D.bounce(D.fullCanvas.ctx, D.fullCanvas.width, D.fullCanvas.height, D.current.fillColor, D.point.x, D.point.y, D.point.r, D.current.x, D.current.y, D.current.r, D.max)
            }
        }
        
        this.drag.addEventListener(D.eventName.start, D.startEvent)
        
    }
    Drag.prototype = {
        offset: function (el) {
            var rect, win,elem = el;
            var rect = elem.getBoundingClientRect();
            var win = elem.ownerDocument.defaultView;
            return {
                top: rect.top + win.pageYOffset,
                left: rect.left + win.pageXOffset
            };    
        },
        tween: {
            /*
             * t: current time(当前时间);
             * b: beginning value(初始值);
             * c: change in value(变化量);
             * d: duration(持续时间)。
            */
            easeOut: function(t, b, c, d) {
                return -c *(t /= d)*(t-2) + b;
            }
        },
        getPoints: function (startX, startY, startR, endX, endY, endR, maxDirection) {
            var D = this;
            // 计算两圆距离
            var currentDirection = Math.sqrt( (endX-startX) * (endX-startX)  + (endY -startY) * (endY -startY) )
            
            // 计算起始圆 半径
            var m = (maxDirection - currentDirection) /  maxDirection;
            
            var startR = startR * 0.3 + startR * 0.7 * (m > 0 ? m : 0);
            // 计算起始圆切点坐标
            var filletB = (endX - startX) / currentDirection ;
            var filletA = (endY - startY) / currentDirection;
            
            
            var startPoint = {
                center: {
                    x: startX,
                    y: startY,
                    r: startR
                },
                a: {
                    x: startX + filletA * startR,
                    y: startY - filletB * startR
                },
                b: {
                    x: startX - filletA * startR,
                    y: startY + filletB * startR
                }
            }
            // 计算结束圆切点坐标
            
            var endPoint = {
                center: {
                    x: endX,
                    y: endY,
                    r: endR
                },
                a: {
                    x: endX + filletA * endR,
                    y: endY - filletB * endR
                },
                b: {
                    x: endX - filletA * endR,
                    y: endY + filletB * endR
                }
            }
            if(Math.abs(currentDirection) > D.max) {
                D.current.canDelate = true;
            }
            if(D.current.canDelate) {
                startPoint = endPoint;
            }
    
            var ctrolPoint = {
                x: startX + (endX-startX)*0.5,
                y: startY + (endY-startY)*0.5,
            }
            
            return {
                startPoint: startPoint,
                endPoint: endPoint,
                ctrolPoint: ctrolPoint,
                currentDirection: currentDirection
            }
        } ,
        drawCercle: function (ctx, ctxWidth, ctxHeight, fillColor,startX, startY, startR, endX, endY, endR, maxDirection) {
            // param:  ctx, ctxWidth, ctxHeight, fillColor,startX, startY, startR, endX, endY, endR, maxDirection
            var D = this;
            var points = D.getPoints(startX, startY, startR, endX, endY, endR, maxDirection);
            var startPoint = points.startPoint;
            var endPoint = points.endPoint;
            
            var ctrolPoint = points.ctrolPoint;
            // 画起始圆
            ctx.clearRect(0,0, ctxWidth, ctxHeight);
            ctx.fillStyle = D.current.fillColor;
            ctx.beginPath();
            ctx.arc(startPoint.center.x,startPoint.center.y,startPoint.center.r,0 , 2*Math.PI);
            ctx.closePath();
            ctx.fill();
            ctx.closePath();
            // 画结束圆
            ctx.beginPath();
            ctx.arc(endPoint.center.x,endPoint.center.y,endPoint.center.r,0, 2*Math.PI);
            ctx.closePath();
            ctx.fill();
            ctx.closePath();
            // 画贝塞尔曲线填充
            ctx.beginPath();
            ctx.moveTo(startPoint.a.x, startPoint.a.y);
            ctx.quadraticCurveTo(ctrolPoint.x, ctrolPoint.y, endPoint.a.x, endPoint.a.y);
            ctx.lineTo(endPoint.b.x, endPoint.b.y);
            ctx.quadraticCurveTo(ctrolPoint.x, ctrolPoint.y, startPoint.b.x, startPoint.b.y);
            ctx.lineTo(startPoint.a.x, startPoint.a.y);
            ctx.closePath();
            ctx.fill();
            // 画文字
            ctx.save()
            ctx.textBaseline = 'middle';
            ctx.font="20px Arial";
            ctx.fillStyle = '#FFF';
            ctx.textAlign='center'
            ctx.fillText(D.texts,endPoint.center.x, endPoint.center.y,D.point.w);
            ctx.restore()
            
            return points.currentDirection
        },
        bounce: function (ctx, ctxWidth, ctxHeight, fillColor,startX, startY, startR, endX, endY, endR, maxDirection) {
            var D = this;
            // 只需要计算随鼠标动的小球的位置 给它做bounce运动就行了
    
            var p = [{},{ // end
                    x: endX,
                    y: endY
                },{ // bef
                    x:  startX - (endX - startX)/2,
                    y:  startY - (endY - startY)/2
                },{ // start
                    x: startX,
                    y: startY
                },
            ]
            var i = 0
            // 弹到圆心
            if(!D.current.canDelate) {
                var timer = setInterval(function (){
                    i++
                    D.drawCercle(ctx, ctxWidth, ctxHeight, fillColor,startX, startY, startR, p[i].x, p[i].y, endR, maxDirection);
                    if(i>= p.length-1) {
                        clearInterval(timer)
                        D.current.canDelate = false
                        D.domBody.removeChild(D.fullCanvas.canvas)
                        D.onDragEnd(false)
                    }
                },80)
            } else {
                D.drawCercle(ctx, ctxWidth, ctxHeight, fillColor,startX, startY, startR, p[p.length-1].x, p[p.length-1].y, endR, maxDirection);
                D.domBody.removeChild(D.fullCanvas.canvas)
                D.current.canDelate = false
                D.onDragEnd(false)
            }
        },
        createCanvas: function (width, height, cssObj) {
            console.log('createCanvas')
            var id = 'canvas' + Math.round(Math.random() * 100000);
            var canvas = document.createElement('canvas');
            canvas.setAttribute('id', id);
            canvas.setAttribute('width', width);
            canvas.setAttribute('height', height);
            for(var item in cssObj) {
                canvas.style[item] = cssObj[item];
            }
            return {canvas: canvas, id: id};
        },
        disappear: function (pos) {
            // pos = {x: x, y: y}  消失点的坐标
            var D = this;
            D.onDragEnd(true)
            var screenWidth = document.documentElement.clientWidth || document.body.clientWidth;
            var timer = null;
            var width, height, i, PI = Math.PI;
            width = height = D.current.r * 5;
    
            var cssObj = {
                'position': 'fixed',
                'left': pos.x - width / 2 + 'px',
                'top': pos.y - height / 2 + 'px'
            }
            var canvasObj = this.createCanvas(width, height, cssObj);
            console.log(canvasObj)
            document.getElementsByTagName('body')[0].appendChild(canvasObj.canvas)
            var canvas = document.getElementById(canvasObj.id);
            var ctx = canvas.getContext('2d');
            var dots = [];
            var dotsLength = Math.round(Math.random() * 3 + 5);
            var currentStep = 0 ,allSteps = 20;
            for (i = 0 ; i < dotsLength; i ++) {
                var r, x, y, a;
                r = D.current.r;
                x = Math.round(Math.random() * (width - r * 2)) + r;
                y = Math.round(Math.random() * (height - r * 2)) + r;
                a = r / allSteps;
                var o = {
                    r: r,
                    x: x,
                    y: y,
                    a: a
                }
                dots.push(o);
            }
            
            function disappear(currentStep ,allSteps) {
                ctx.clearRect(0, 0, width,height);
    //                        ctx.fillStyle = D.current.fillColor;
                ctx.fillStyle = '#D4D4D4';
                
                for(i = 0; i < dots.length; i ++)  {
                    ctx.beginPath();
                    ctx.arc(
                        D.tween.easeOut(currentStep, width/2, dots[i].x - width/2, allSteps),
                        D.tween.easeOut(currentStep, height/2, dots[i].y - height/2, allSteps),
                        D.tween.easeOut(currentStep, dots[i].r, -dots[i].r , allSteps), 
                        0, 2*PI);
                    ctx.closePath();
                    ctx.fill();
                }
            }
            disappear(currentStep ,allSteps)
            timer = setInterval(function (){
                currentStep ++ ;
                disappear(currentStep ,allSteps)
                if(currentStep >= allSteps) {
                    clearInterval(timer)
                    console.log(canvas)
                    document.getElementsByTagName('body')[0].removeChild(canvas)
                    D.onDelated()
                }
            }, 40)
        }
    }
    
    
    
    function MoveDrag (params) {
        // params = {
        //    doms: '',  // 元素,可以是类名或者是id或者是直接获取到的元素// 本期只支持类名
        //    max: 70m
        // fillColor: ''
        // }
        var M = this;
        this.defaults = {
            doms: '',
            max: 70,
            fillcolor: ''
        }
        this.defaults = Object.assign(this.defaults, params) 
        
        var d = this.defaults.doms;
        
        if(typeof(d) == 'string') {
            if(/^./.test(d)) {
                // 类名
                this.defaults.doms = document.getElementsByClassName(d.replace(/^./,''));
            } else if (/^#/.test(d)){
                // id名
                var _dm = document.getElementById(d)
                if(_dm) {
                    this.defaults.doms = [document.getElementById(d.replace(/^#/,''))];
                } else {
                    this.defaults.doms = [];
                }
            } else {
                // 标签名
                this.defaults.doms = document.getElementsByTagName(d);
            }
        } else {
            // 不知道是啥玩意儿
        }
        var obj = [];
        
        for (var i = 0; i < this.defaults.doms.length; i ++) {
            var o = new Drag({
                drag: M.defaults.doms[i],
                max: M.defaults.max,
                fillColor: M.defaults.fillColor
            })
            obj.push(o)
        }
        return obj;
        
    }
    new MoveDrag({
        doms: '.dragdom',
        max: 90,
        fillcolor: 'red'
    })

    (完)

  • 相关阅读:
    Dropplets – 极简的 Markdown 博客平台
    T3
    Awesomplete
    SVG Drawing Animation
    Dom Animator – 提供 Dom 注释动画的 JS 库
    Electron
    很赞的效果!互动的页面元素拖放着色实验
    BookBlock
    雷军投资的理财网站,年化收益13%!
    Rainyday.js – 使用 JavaScript 实现雨滴效果
  • 原文地址:https://www.cnblogs.com/hanguozhi/p/9543592.html
Copyright © 2011-2022 走看看