原生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: ‘‘, ???????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拖拽删除
原文地址:https://www.cnblogs.com/hanguozhi/p/9543592.html