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:;}
.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: '',
        width: 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'
})
(完)
js仿QQ拖拽删除的更多相关文章
- iOS 未读消息角标 仿QQ拖拽 简单灵活 支持xib(源码)
		一.效果 二.简单用法 超级简单,2行代码集成:xib可0代码集成,只需拖一个view关联LFBadge类即可 //一般view上加角标 _badge1 = [[LFBadge alloc] init ... 
- 手把手实现腾讯qq拖拽删去效果(一)
		qq拖拽删除的效果,简单又好用,今天我就叫大家实现吧. 这个滑动效果,有何难点了,就是响应每行的点击事件了,为了完成这个任务,并且能够实现动画的效果了,我重写了一个slideview这个控件,这个控件 ... 
- 【Swift 4.0】iOS 11 UICollectionView 长按拖拽删除崩溃的问题
		正文 功能 用 UICollectionView 实现两个 cell 之间的位置交互或者拖拽某个位置删除 问题 iOS 11 以上拖拽删除会崩溃,在 iOS 9.10 都没有问题 错误 017-10- ... 
- JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果
		JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果 今天是2014年第一篇博客是关于类似于我们的qq空间长图片展示效果,因为一张很长的图片不可能全部把他展示出来,所以外层用了一个容器给他一个高度,超 ... 
- php和js实现文件拖拽上传
		Dropzone.js实现文件拖拽上传 http://www.sucaihuo.com/php/1399.html demo http://www.sucaihuo.com/jquery/13/139 ... 
- JS  Event 鼠标拖拽事件
		<!DOCTYPE html><html> <head> <meta charset="UTF-8"> ... 
- Dropzone.js实现文件拖拽上传
		dropzone.js是一个开源的JavaScript库,提供 AJAX 异步文件上传功能,支持拖拽文件.支持最大文件大小.支持设置文件类型.支持预览上传结果,不依赖jQuery库. 使用Dropzo ... 
- 纯JS实现可拖拽表单
		转载注明出处!!! 转载注明出处!!! 转载注明出处!!! 因为要用到可拖拽表单,个人要比较喜欢自己动手,不怎么喜欢在不懂实现或者原理的情况下用插件,所以查找资料实现了一个. 思路:放入:用mouse ... 
- Vue.Draggable:基于 Sortable.js 的 Vue 拖拽组件使用中遇到的问题
		Sortable.js 介绍 https://segmentfault.com/a/1190000008209715 项目中遇到的问题: A - 我需要在项目的拖拽组件中,使用背景 1 - 想到的第一 ... 
随机推荐
- post提交方式
			post提交方式 为提交 url 路径后的name值 getParameter 是获取url后面的参数的.getattribute 是获取 自己setattribute的. 
- Python语言简介以及特点
			编程语言的分为编译型语言和解释型语言: 1. 编译型语言: (1) 编译型语言的代表:C.C++.Delphi等, (2) 编译型语言的运行方式:编译 -> 运行 (3) 编译型语言的优缺点分析 ... 
- lua编程之协程介绍
			一,lua协程简介 协程(coroutine),意思就是协作的例程,最早由Melvin Conway在1963年提出并实现.跟主流程序语言中的线程不一样,线程属于侵入式组件,线程实现的系统称之为抢占式 ... 
- Fortran的数组与指针
			个人理解,欢迎指正 指针就是记录数据的内存地址的变量.指针可以指向单个变量,也可以指向数组. 数组是一个概念,是若干个类型相同的元素的有序集合.在Fortran语言中,数组中存放的元素,可以是整数,实 ... 
- Linux命令应用大词典-第1章 登录、退出、关机和重启
			1.1 login:用户登录系统 1.2 logout:退出登录shell 1.3 nologin:限制用户登录 1.4 exit:退出shell 1.5 sulogin:单用户登录(single u ... 
- sqlite导入mysql
			在线阅读地址 http://wenku.baidu.com/view/cc6821a8482fb4daa58d4bb8 
- [Unity3D]MonoDeveloper快捷键(补全代码补全引用中文乱码tab转空格)
			Hello亲爱的观众朋友们大家好,我是09. vs支持各种插件,一般推荐用vs.不过总有人(例如我)由于各种原因用MonoDeveloper.苦于每次上网找各种设置,此处集中写下我用MonoDevel ... 
- 环境变量的配置-java-JMETER - 【Linux】
			rz上传 lz下载 步骤: . Linux下首先安装Jdk: . 下载apache-jmeter-4.0.tgz,复制到Linux系统中的/opt目录下: . 解压apache-jmeter-4.0. ... 
- 学习python,第二篇
			注释 # 单行注释 ''' 多行注释 ''' 或者 """ 多行注释 """ # Author: Itxpl mag ... 
- [奇葩问题] ERROR 2013 (HY000): Lost connection to MySQL server during query
			查询一条耗时30s以上语句,实际为2分钟多. mysql> select version(); +------------+ | version() | +------------+ | 5.6 ... 
