拖拽
基本实现思路(mouse事件替代)
滑到盒子上,按住盒子;
鼠标走,盒子拖着走;
结束了,松开鼠标,即抬起;
mousedown 按下
mousemove 跟着走
mouseup 抬起
核心思想:
按下的时候记录起始位置,移动过程中计算偏移+盒子起始位置,设置盒子位置。
但是这是最low最low的版本
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>drag</title>
<style>
*{
margin: 0;
padding:0;
}
.box{
position: absolute;
left: 100px;
top: 100px;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<script>
let drag=false;
// id可以直接当做dom用
box.onmousedown=function (ev) {
console.log('onmousedown===');
drag=true;
this.mouseStartX=ev.pageX;
this.mouseStartY=ev.pageY;
this.startX=box.offsetLeft;
this.startY=box.offsetTop;
}
box.onmousemove=function (ev) {
console.log('onmousemove===');
if(!drag) return false;
let curLeft=ev.pageX-this.mouseStartX+this.startX;
let curTop=ev.pageY-this.mouseStartY+this.startY;
this.style.left=`${curLeft}px`;
this.style.top=`${curTop}px`;
}
box.onmouseup=function (ev) {
console.log('onmouseup===');
drag=false;
}
// box.ondragend=function(ev){
// console.log('ondragend======');
// console.log(ev)
// console.log(ev.offsetX);
// console.log(ev.offsetY);
//
// }
// box.ondragstart=function(ev){
// console.log('ondragstart======');
// console.log(ev)
// console.log(ev.offsetX);
// console.log(ev.offsetY);
//
// }
</script>
</body>
</html>
问题
优化一:move、up事件绑定
不是一进来就绑方法,按下去的时候才绑。按下去的时候做什么才有作用。
按下才表示拖拽要开始。mousemove mouseup事件绑定要在mousedown触发之后;
同理up时移除掉。
鼠标焦点丢失问题
出现问题原因:丢掉焦点出现问题的原因就是,鼠标移动过快,盒子跟不上。松起来是在盒子外面松起来,盒子事件没取消掉。
绑document:推荐
setCapture
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>drag</title>
<style>
*{
margin: 0;
padding:0;
}
html,body{
height: 100%;
}
.box{
position: absolute;
left: 100px;
top: 100px;
width: 100px;
height: 100px;
background: red;
}
</style>
</head>
<body>
<div class="box" id="box"></div>
<script>
function mousedown(ev){
this.mouseStartX=ev.pageX;
this.mouseStartY=ev.pageY;
this.startX=box.offsetLeft;
this.startY=box.offsetTop;
document.onmousemove=mousemove.bind(box);
document.onmouseup=mouseup;
}
function mousemove(ev){
let curLeft=ev.pageX-this.mouseStartX+this.startX;
let curTop=ev.pageY-this.mouseStartY+this.startY;
let minLeft=0,maxLeft=document.body.clientWidth-this.offsetWidth,minTop=0,maxTop=document.body.clientHeight-this.offsetHeight;
curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
this.style.left=`${curLeft}px`;
this.style.top=`${curTop}px`;
}
function mouseup(ev){
this.onmousemove=null;
this.onmouseup=null;
}
// id可以直接当做dom用
box.onmousedown=mousedown;
</script>
</body>
</html>
使用dom2级事件绑定
绑定的时候好绑,我以后想移除该怎么移除。所以2级dom事件绑定的时候一般都用实名函数,不用匿名函数。
move另一个方法要拿到,那么做成自定义属性。
复习拖拽的步骤
把上述内容梳理了一遍
手指按下代表拖拽开始,只要鼠标在盒子上移动盒子就跟着动。
抬起表示拖拽结束,鼠标再移动就要没效果。
拖拽的整个流程规划:
啥时候开始拖拽,啥时候鼠标移动有效果,啥时候没效果。整体流程规划。
鼠标按下绑定move事件,move事件计算,up事件移除。
鼠标移动多远盒子也移动多远。
鼠标当前-鼠标开始+盒子开始=盒子当前位置
一些涉及到的知识点:
开始信息存储:
全局变量容易污染。
盒子存东西用自定义属性
- offsetLeft( offset() ) 此案例中父级参照物正好是body
- 获取left直接通过样式中的left。所有经过浏览器计算的样式属性。
鼠标移动过快焦点丢失问题:
外边鼠标抬起了,不是在盒子上,所以盒子的mouseup没有被触发。
鼠标脱离盒子了,鼠标的事情跟盒子没关系了。
第一步:搭结构。什么时候绑定事件方法,什么时候移除。
第二步:拖拽步骤计算位置
第三步:过快焦点丢失。绑document->this问题;自定义属性存储move函数。
需求升级:Drag事件
拖到一个容器中
drag实现
把一个元素放到指定区域里面
draggable=true 可拖拽元素
dragstart dataTransfer存储数据,把其他方法中要用到的数据事先存储起来
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>drag</title>
<style>
*{
margin: 0;
padding:0;
}
html,body{
height: 100%;
}
.box{
cursor: move;
position: absolute;
width: 100px;
height: 100px;
background: red;
}
.container{
position: relative;
width: 600px;
height: 400px;
background: #9fcdff;
top: 50px;
left: 500px;
}
</style>
</head>
<body>
<div class="box" id="box" draggable="true"></div>
<div class="container" id="container"></div>
<script>
box.ondragstart=function (ev) {
ev.dataTransfer.setData('text/plain','box');
}
container.ondragover=function (ev) {
ev.preventDefault()
}
container.ondrop=function (ev) {
let id=ev.dataTransfer.getData('text/plain')
let box=document.getElementById(id)
// ev.preventDefault()
container.appendChild(box);
}
</script>
</body>
</html>
补充
dataTransfer中setData的东西只能是在ondrop事件中通过getData拿到
mouse事件要判断范围,是否在象限内
但是会有一些问题:
dragover要ev.preventDefault()
drag事件是拖拽了盒子的阴影,不是盒子
项目中拖动效果其实还是用mouse用得多
只是某些业务场景,比如拖到一个容器里可能用drag会更加简单些
案例:百度模态框拖拽
扩展:操作自定义属性
- setAttribute:加在html结构中,设置在元素的行内属性上
- this.xxx: 给堆内存空间直接设置自定义属性。操作堆内存
原生this和jquery $this各自的好处:
原生this:原生的永远都是一个,就是这一个堆
$this:能用jQuery方法, 操作起来更加方便
实现居中的方式:
top left 50%:
- margin:负宽高一半
- transform:平移
- flex布局:center
- margin:auto 和 4个方向都是0+position:absolute
- 用js实现
【css】盒子水平垂直居中的实现_儒rs的博客-CSDN博客
这里要用js实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="../css/bootstrap.min.css">
<style>
html,body{
height: 100%;
}
.modal{
width: 500px;
height: 264px;
}
.modal .modal-dialog{
margin: 0;
}
.modal-header{
cursor: move;
}
</style>
</head>
<body>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" id="loginButton">
百度登录
</button>
<!-- Modal -->
<div class="modal fade show" id="loginModal" draggable="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">百度登录</h5>
<button type="button" class="close" id="closeButton">
<span>×</span>
</button>
</div>
<div class="modal-body">
夫君子之行,静以修身,俭以养德。非淡泊无以明志,非宁静无以致远。夫学须静也,才须学也,非学无以广才,非志无以成学。淫慢则不能励精,险躁则不能治性。年与时驰,意与日去,遂成枯落,多不接世,悲守穷庐,将复何及!
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary">提交</button>
</div>
</div>
</div>
</div>
<script src="../js/jquery-1.11.3.min.js"></script>
<script>
let $loginButton=$('#loginButton'),$loginModal=$('#loginModal'),$closeButton=$('#closeButton');
$loginButton.click(function () {
// 用js实现居中
let top=($(window).outerHeight()-$loginModal.outerHeight())/2;
let left=($(window).outerWidth()-$loginModal.outerWidth())/2;
$loginModal.css({
display:'block',
top,
left,
})
})
$closeButton.click(function () {
$loginModal.css({
display:'none'
})
})
const modalHeader=document.querySelector('.modal .modal-header'),modal=document.querySelector('.modal')
// 拖拽,同之前一样
modalHeader.addEventListener('mousedown',mousedown.bind(modal))
modalHeader.ondrag=function (ev) {
ev.preventDefault();
}
document.body.ondragover=function (ev) {
ev.preventDefault()
ev.stopPropagation()
}
function mousedown(ev){
if(ev.target.className!=='modal-header' || (ev.offsetX<0 && ev.offsetY<0)) return;
this.mouseStartX=ev.pageX;
this.mouseStartY=ev.pageY;
this.startX=this.offsetLeft;
this.startY=this.offsetTop;
this.move=mousemove.bind(this);
this.up=mouseup.bind(this)
document.onmousemove=this.move;
document.onmouseup=this.up;
// document.addEventListener('mousemove',this.move)
// document.addEventListener('mouseup',this.up)
}
function mousemove(ev){
let curLeft=ev.pageX-this.mouseStartX+this.startX;
let curTop=ev.pageY-this.mouseStartY+this.startY;
let minLeft=0,maxLeft=document.body.clientWidth-this.offsetWidth,minTop=0,maxTop=document.body.clientHeight-this.offsetHeight;
curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
this.style.left=`${curLeft}px`;
this.style.top=`${curTop}px`;
}
function mouseup(ev){
// document.removeEventListener('mousemove',this.move)
// document.removeEventListener('mouseup',this.up)
document.onmousemove=null;
document.onmouseup=null;
}
</script>
</body>
</html>
拖拽插件封装一:封装插件技能点
尽可能保证每个方法中的this都是当前类的实例
参数初始化:
全部挂载到实例上
options与defaultOptions合并替换
传的替换,不传的使用默认值
assign的局限性:
只做一层的拷贝
工具方法each:
涉及到知识点:
数据类型的检测
回调函数的使用
对象、数组的循环
数组&类数组的特点
支持返回值
……
(function () {
class Drag{
constructor(selector,options){
this.init(selector,options)
}
init(selector,options){
this._selector=document.querySelector(selector);
const defaultOptions={
element:this._selector,
boundary:true,
dragstart:null,
dragmove:null,
dragend:null
}
options=Object.assign(defaultOptions,options)
Drag.each(options,(value,key)=>{
this[`_${key}`]=value;
})
console.log(this)
}
static each(iterator,callback){
const type=Drag.typeof(iterator);
if(type==='Array'){
for(let i=0;i<iterator.length;i++){
callback(iterator[i],i)
}
}else if(type==='Object'){
for(const key in iterator){
if(iterator.hasOwnProperty(key)){
callback(iterator[key],key)
}
}
}
}
static typeof(obj){
return Object.prototype.toString.call(obj).replace(/\[object |\]/g,'')
}
}
window.Drag=Drag;
})()
拖拽插件封装二:实现具体的功能
实现拖拽效果
drag事件:dragover默认行为阻止掉
call & apply & bind:
三者区别:
call&apply立即执行,bind预先改变this,没有立即执行。
apply传参是数组
源码
应用场景:
dom事件绑定的时候,改变this;
定时器执行:默认是window。使用bind改变this。之后才做,做的时候this改成我想要的。
call&apply把函数执行了,改了this。
在钩子函数中,把一些信息通过参数传递过去
(function () {
class Drag{
constructor(selector,options){
this.init(selector,options)
this._selector.addEventListener('mousedown',this.down.bind(this))
}
init(selector,options){
this._selector=document.querySelector(selector);
const defaultOptions={
element:this._selector,
boundary:true,
dragstart:null,
dragmove:null,
dragend:null
}
options=Object.assign(defaultOptions,options)
Drag.each(options,(value,key)=>{
this[`_${key}`]=value;
})
console.log(this)
}
down(ev){
let {_element}=this;
this.mouseStartX=ev.pageX;
this.mouseStartY=ev.pageY;
this.startX=Number.parseFloat(Drag.queryCss(_element,'left'));
this.startY=Number.parseFloat(Drag.queryCss(_element,'top'));
this._move=this.move.bind(this);
this._up=this.up.bind(this)
document.addEventListener('mousemove',this._move)
document.addEventListener('mouseup',this._up)
this.dragstart && this.dragstart(this,ev);
}
move(ev){
let {mouseStartX,mouseStartY,startX,startY,_element,_boundary}=this;
let curLeft=ev.pageX-mouseStartX+startX;
let curTop=ev.pageY-mouseStartY+startY;
if(_boundary){
// 已约定要相对于父盒子定位
let parent=_element.parentNode;
let minLeft=0,maxLeft=parent.offsetWidth-_element.offsetWidth,minTop=0,maxTop=parent.offsetHeight-_element.offsetHeight;
curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
}
_element.style.left=`${curLeft}px`;
_element.style.top=`${curTop}px`;
this.dragmove && this.dragmove(this,curLeft,curTop,ev)
}
up(ev){
document.removeEventListener('mousemove',this._move)
document.removeEventListener('mouseup',this._up)
this.dragend && this.dragend(this,ev)
}
static each(iterator,callback){
const type=Drag.typeof(iterator);
if(type==='Array'){
for(let i=0;i<iterator.length;i++){
callback(iterator[i],i)
}
}else if(type==='Object'){
for(const key in iterator){
if(iterator.hasOwnProperty(key)){
callback(iterator[key],key)
}
}
}
}
static typeof(obj){
return Object.prototype.toString.call(obj).replace(/\[object |\]/g,'')
}
static queryCss(elem,attr){
return window.getComputedStyle(elem)[attr];
}
}
window.Drag=Drag;
})()
插件封装:
class constructor
分析配置哪些参数