原生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拖拽删除的更多相关文章

  1. iOS 未读消息角标 仿QQ拖拽 简单灵活 支持xib(源码)

    一.效果 二.简单用法 超级简单,2行代码集成:xib可0代码集成,只需拖一个view关联LFBadge类即可 //一般view上加角标 _badge1 = [[LFBadge alloc] init ...

  2. 手把手实现腾讯qq拖拽删去效果(一)

    qq拖拽删除的效果,简单又好用,今天我就叫大家实现吧. 这个滑动效果,有何难点了,就是响应每行的点击事件了,为了完成这个任务,并且能够实现动画的效果了,我重写了一个slideview这个控件,这个控件 ...

  3. 【Swift 4.0】iOS 11 UICollectionView 长按拖拽删除崩溃的问题

    正文 功能 用 UICollectionView 实现两个 cell 之间的位置交互或者拖拽某个位置删除 问题 iOS 11 以上拖拽删除会崩溃,在 iOS 9.10 都没有问题 错误 017-10- ...

  4. JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果

    JS仿QQ空间鼠标停在长图片时候图片自动上下滚动效果 今天是2014年第一篇博客是关于类似于我们的qq空间长图片展示效果,因为一张很长的图片不可能全部把他展示出来,所以外层用了一个容器给他一个高度,超 ...

  5. php和js实现文件拖拽上传

    Dropzone.js实现文件拖拽上传 http://www.sucaihuo.com/php/1399.html demo http://www.sucaihuo.com/jquery/13/139 ...

  6. JS Event 鼠标拖拽事件

    <!DOCTYPE html><html> <head>        <meta charset="UTF-8">         ...

  7. Dropzone.js实现文件拖拽上传

    dropzone.js是一个开源的JavaScript库,提供 AJAX 异步文件上传功能,支持拖拽文件.支持最大文件大小.支持设置文件类型.支持预览上传结果,不依赖jQuery库. 使用Dropzo ...

  8. 纯JS实现可拖拽表单

    转载注明出处!!! 转载注明出处!!! 转载注明出处!!! 因为要用到可拖拽表单,个人要比较喜欢自己动手,不怎么喜欢在不懂实现或者原理的情况下用插件,所以查找资料实现了一个. 思路:放入:用mouse ...

  9. Vue.Draggable:基于 Sortable.js 的 Vue 拖拽组件使用中遇到的问题

    Sortable.js 介绍 https://segmentfault.com/a/1190000008209715 项目中遇到的问题: A - 我需要在项目的拖拽组件中,使用背景 1 - 想到的第一 ...

随机推荐

  1. [VB.NET][C#]WAV格式文件头部解析

    简介 WAV 为微软开发的一种声音文件格式,它符合 RIFF(Resource Interchange File Format)文件规范,用于保存 Windows 平台的音频信息资源. 第一节 文件头 ...

  2. Rxjava - 操作符,线程操作的简单使用

    目录 创建操作符 10种常用的操作符定义 下面做几个操作符的demo演示 create from repeat defer interval Scheduler 什么是Scheduler? 如何使用S ...

  3. Cisco Packet Tracer中通过集线器组网

    Cisco Packet Tracer中可以通过集线器将多台电脑完成通信. Cisco Packet Tracer 6.2.0 一.添加三台电脑设备 1.按照下图1.2步骤操作,2步骤执行三次,拖拽P ...

  4. JavaScript学习笔记(六)—— 异步编码

    第七章 异步编码 1  事件处理程序 处理程序:即网页加载完毕后将执行的代码,称回调函数或监听器: 包含:处理函数+window.onload=函数名; <script language=&qu ...

  5. Java中 static、final和static final的特点及区别

    final: final可以修饰:属性,方法,类,局部变量(方法中的变量) final修饰的属性的初始化可以在编译期,也可以在运行时,初始化后不能被改变. final修饰的属性跟具体对象有关,在运行期 ...

  6. 重磅发布 | 黑镜调查:深渊背后的真相之「DDoS 威胁与黑灰产业调查报告」

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由云鼎实验室发表于云+社区专栏 本文经授权转载自 FreeBuf 2018年世界杯硝烟散尽,但关于她的话题却远远没有结束.说起世界杯,就 ...

  7. Ubuntu系统无法识别Logitech M590蓝牙鼠标的问题

    参见 - https://blog.csdn.net/yh2869/article/details/73119018 亲测可用. 系统:ubuntu 16.04 64bit 现象:鼠标配对可以成功,但 ...

  8. 教你用Python解决非平衡数据问题(附代码)

    本文为你分享数据挖掘中常见的非平衡数据的处理,内容涉及到非平衡数据的解决方案和原理,以及如何使用Python这个强大的工具实现平衡的转换. 后台回复“不平衡”获取数据及代码~ 前言 好久没有更新自己写 ...

  9. R语言安装R package的2种方法

    http://www.cnblogs.com/emanlee/archive/2012/12/05/2803606.html

  10. 详解HTTP缓存

    HTTP缓存是个大公司面试几乎必考的问题,写篇随笔说一下HTTP缓存. 1. HTTP报文首部中有关缓存的字段 在HTTP报文中,与缓存相关的信息都存在首部里,简单说一下首部. 首部 HTTP首部字段 ...