• js中的拖拽


    拖拽

    基本实现思路(mouse事件替代)

    滑到盒子上,按住盒子;

    鼠标走,盒子拖着走;

    结束了,松开鼠标,即抬起;

    mousedown 按下

    mousemove 跟着走

    mouseup 抬起

    核心思想:

    按下的时候记录起始位置,移动过程中计算偏移+盒子起始位置,设置盒子位置。

    但是这是最low最low的版本

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>drag</title>
    6. <style>
    7. *{
    8. margin: 0;
    9. padding:0;
    10. }
    11. .box{
    12. position: absolute;
    13. left: 100px;
    14. top: 100px;
    15. width: 100px;
    16. height: 100px;
    17. background: red;
    18. }
    19. </style>
    20. </head>
    21. <body>
    22. <div class="box" id="box"></div>
    23. <script>
    24. let drag=false;
    25. // id可以直接当做dom用
    26. box.onmousedown=function (ev) {
    27. console.log('onmousedown===');
    28. drag=true;
    29. this.mouseStartX=ev.pageX;
    30. this.mouseStartY=ev.pageY;
    31. this.startX=box.offsetLeft;
    32. this.startY=box.offsetTop;
    33. }
    34. box.onmousemove=function (ev) {
    35. console.log('onmousemove===');
    36. if(!drag) return false;
    37. let curLeft=ev.pageX-this.mouseStartX+this.startX;
    38. let curTop=ev.pageY-this.mouseStartY+this.startY;
    39. this.style.left=`${curLeft}px`;
    40. this.style.top=`${curTop}px`;
    41. }
    42. box.onmouseup=function (ev) {
    43. console.log('onmouseup===');
    44. drag=false;
    45. }
    46. // box.ondragend=function(ev){
    47. // console.log('ondragend======');
    48. // console.log(ev)
    49. // console.log(ev.offsetX);
    50. // console.log(ev.offsetY);
    51. //
    52. // }
    53. // box.ondragstart=function(ev){
    54. // console.log('ondragstart======');
    55. // console.log(ev)
    56. // console.log(ev.offsetX);
    57. // console.log(ev.offsetY);
    58. //
    59. // }
    60. </script>
    61. </body>
    62. </html>

    问题

    优化一:move、up事件绑定

    不是一进来就绑方法,按下去的时候才绑。按下去的时候做什么才有作用。

    按下才表示拖拽要开始。mousemove mouseup事件绑定要在mousedown触发之后;

    同理up时移除掉。

    鼠标焦点丢失问题

    出现问题原因:丢掉焦点出现问题的原因就是,鼠标移动过快,盒子跟不上。松起来是在盒子外面松起来,盒子事件没取消掉。

    绑document:推荐

    setCapture

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>drag</title>
    6. <style>
    7. *{
    8. margin: 0;
    9. padding:0;
    10. }
    11. html,body{
    12. height: 100%;
    13. }
    14. .box{
    15. position: absolute;
    16. left: 100px;
    17. top: 100px;
    18. width: 100px;
    19. height: 100px;
    20. background: red;
    21. }
    22. </style>
    23. </head>
    24. <body>
    25. <div class="box" id="box"></div>
    26. <script>
    27. function mousedown(ev){
    28. this.mouseStartX=ev.pageX;
    29. this.mouseStartY=ev.pageY;
    30. this.startX=box.offsetLeft;
    31. this.startY=box.offsetTop;
    32. document.onmousemove=mousemove.bind(box);
    33. document.onmouseup=mouseup;
    34. }
    35. function mousemove(ev){
    36. let curLeft=ev.pageX-this.mouseStartX+this.startX;
    37. let curTop=ev.pageY-this.mouseStartY+this.startY;
    38. let minLeft=0,maxLeft=document.body.clientWidth-this.offsetWidth,minTop=0,maxTop=document.body.clientHeight-this.offsetHeight;
    39. curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
    40. curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
    41. this.style.left=`${curLeft}px`;
    42. this.style.top=`${curTop}px`;
    43. }
    44. function mouseup(ev){
    45. this.onmousemove=null;
    46. this.onmouseup=null;
    47. }
    48. // id可以直接当做dom用
    49. box.onmousedown=mousedown;
    50. </script>
    51. </body>
    52. </html>

    使用dom2级事件绑定

    绑定的时候好绑,我以后想移除该怎么移除。所以2级dom事件绑定的时候一般都用实名函数,不用匿名函数。

    move另一个方法要拿到,那么做成自定义属性

    复习拖拽的步骤

    把上述内容梳理了一遍

    手指按下代表拖拽开始,只要鼠标在盒子上移动盒子就跟着动。

    抬起表示拖拽结束,鼠标再移动就要没效果。

    拖拽的整个流程规划:

    啥时候开始拖拽,啥时候鼠标移动有效果,啥时候没效果。整体流程规划。

    鼠标按下绑定move事件,move事件计算,up事件移除。

    鼠标移动多远盒子也移动多远。

    鼠标当前-鼠标开始+盒子开始=盒子当前位置

    一些涉及到的知识点:

    开始信息存储:

    全局变量容易污染。

    盒子存东西用自定义属性

    • offsetLeft( offset() ) 此案例中父级参照物正好是body
    • 获取left直接通过样式中的left。所有经过浏览器计算的样式属性。

    鼠标移动过快焦点丢失问题:

    外边鼠标抬起了,不是在盒子上,所以盒子的mouseup没有被触发。

    鼠标脱离盒子了,鼠标的事情跟盒子没关系了。

    第一步:搭结构。什么时候绑定事件方法,什么时候移除。

    第二步:拖拽步骤计算位置

    第三步:过快焦点丢失。绑document->this问题;自定义属性存储move函数。

    需求升级:Drag事件

    拖到一个容器中

    drag实现

    把一个元素放到指定区域里面

    draggable=true 可拖拽元素

    dragstart dataTransfer存储数据,把其他方法中要用到的数据事先存储起来

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>drag</title>
    6. <style>
    7. *{
    8. margin: 0;
    9. padding:0;
    10. }
    11. html,body{
    12. height: 100%;
    13. }
    14. .box{
    15. cursor: move;
    16. position: absolute;
    17. width: 100px;
    18. height: 100px;
    19. background: red;
    20. }
    21. .container{
    22. position: relative;
    23. width: 600px;
    24. height: 400px;
    25. background: #9fcdff;
    26. top: 50px;
    27. left: 500px;
    28. }
    29. </style>
    30. </head>
    31. <body>
    32. <div class="box" id="box" draggable="true"></div>
    33. <div class="container" id="container"></div>
    34. <script>
    35. box.ondragstart=function (ev) {
    36. ev.dataTransfer.setData('text/plain','box');
    37. }
    38. container.ondragover=function (ev) {
    39. ev.preventDefault()
    40. }
    41. container.ondrop=function (ev) {
    42. let id=ev.dataTransfer.getData('text/plain')
    43. let box=document.getElementById(id)
    44. // ev.preventDefault()
    45. container.appendChild(box);
    46. }
    47. </script>
    48. </body>
    49. </html>

    补充

    dataTransfer中setData的东西只能是在ondrop事件中通过getData拿到

    mouse事件要判断范围,是否在象限内

    但是会有一些问题:

    dragover要ev.preventDefault()

    drag事件是拖拽了盒子的阴影,不是盒子

    项目中拖动效果其实还是用mouse用得多

    只是某些业务场景,比如拖到一个容器里可能用drag会更加简单些

    案例:百度模态框拖拽

    扩展:操作自定义属性

    1. setAttribute:加在html结构中,设置在元素的行内属性
    2. this.xxx: 给堆内存空间直接设置自定义属性。操作堆内存

    原生this和jquery $this各自的好处:

    原生this:原生的永远都是一个,就是这一个堆

    $this:能用jQuery方法, 操作起来更加方便

    实现居中的方式:

    top left 50%:

    1. margin:负宽高一半
    2. transform:平移
    3. flex布局:center
    4. margin:auto 和 4个方向都是0+position:absolute
    5. 用js实现

    【css】盒子水平垂直居中的实现_儒rs的博客-CSDN博客

    这里要用js实现

    1. <!DOCTYPE html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>Title</title>
    6. <link rel="stylesheet" href="../css/bootstrap.min.css">
    7. <style>
    8. html,body{
    9. height: 100%;
    10. }
    11. .modal{
    12. width: 500px;
    13. height: 264px;
    14. }
    15. .modal .modal-dialog{
    16. margin: 0;
    17. }
    18. .modal-header{
    19. cursor: move;
    20. }
    21. </style>
    22. </head>
    23. <body>
    24. <!-- Button trigger modal -->
    25. <button type="button" class="btn btn-primary" id="loginButton">
    26. 百度登录
    27. </button>
    28. <!-- Modal -->
    29. <div class="modal fade show" id="loginModal" draggable="false">
    30. <div class="modal-dialog">
    31. <div class="modal-content">
    32. <div class="modal-header">
    33. <h5 class="modal-title">百度登录</h5>
    34. <button type="button" class="close" id="closeButton">
    35. <span>&times;</span>
    36. </button>
    37. </div>
    38. <div class="modal-body">
    39. 夫君子之行,静以修身,俭以养德。非淡泊无以明志,非宁静无以致远。夫学须静也,才须学也,非学无以广才,非志无以成学。淫慢则不能励精,险躁则不能治性。年与时驰,意与日去,遂成枯落,多不接世,悲守穷庐,将复何及!
    40. </div>
    41. <div class="modal-footer">
    42. <button type="button" class="btn btn-primary">提交</button>
    43. </div>
    44. </div>
    45. </div>
    46. </div>
    47. <script src="../js/jquery-1.11.3.min.js"></script>
    48. <script>
    49. let $loginButton=$('#loginButton'),$loginModal=$('#loginModal'),$closeButton=$('#closeButton');
    50. $loginButton.click(function () {
    51. // 用js实现居中
    52. let top=($(window).outerHeight()-$loginModal.outerHeight())/2;
    53. let left=($(window).outerWidth()-$loginModal.outerWidth())/2;
    54. $loginModal.css({
    55. display:'block',
    56. top,
    57. left,
    58. })
    59. })
    60. $closeButton.click(function () {
    61. $loginModal.css({
    62. display:'none'
    63. })
    64. })
    65. const modalHeader=document.querySelector('.modal .modal-header'),modal=document.querySelector('.modal')
    66. // 拖拽,同之前一样
    67. modalHeader.addEventListener('mousedown',mousedown.bind(modal))
    68. modalHeader.ondrag=function (ev) {
    69. ev.preventDefault();
    70. }
    71. document.body.ondragover=function (ev) {
    72. ev.preventDefault()
    73. ev.stopPropagation()
    74. }
    75. function mousedown(ev){
    76. if(ev.target.className!=='modal-header' || (ev.offsetX<0 && ev.offsetY<0)) return;
    77. this.mouseStartX=ev.pageX;
    78. this.mouseStartY=ev.pageY;
    79. this.startX=this.offsetLeft;
    80. this.startY=this.offsetTop;
    81. this.move=mousemove.bind(this);
    82. this.up=mouseup.bind(this)
    83. document.onmousemove=this.move;
    84. document.onmouseup=this.up;
    85. // document.addEventListener('mousemove',this.move)
    86. // document.addEventListener('mouseup',this.up)
    87. }
    88. function mousemove(ev){
    89. let curLeft=ev.pageX-this.mouseStartX+this.startX;
    90. let curTop=ev.pageY-this.mouseStartY+this.startY;
    91. let minLeft=0,maxLeft=document.body.clientWidth-this.offsetWidth,minTop=0,maxTop=document.body.clientHeight-this.offsetHeight;
    92. curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
    93. curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
    94. this.style.left=`${curLeft}px`;
    95. this.style.top=`${curTop}px`;
    96. }
    97. function mouseup(ev){
    98. // document.removeEventListener('mousemove',this.move)
    99. // document.removeEventListener('mouseup',this.up)
    100. document.onmousemove=null;
    101. document.onmouseup=null;
    102. }
    103. </script>
    104. </body>
    105. </html>

    拖拽插件封装一:封装插件技能点

    尽可能保证每个方法中的this都是当前类的实例

    参数初始化:

    全部挂载到实例上

    options与defaultOptions合并替换

    传的替换,不传的使用默认值

    assign的局限性:

    只做一层的拷贝

    工具方法each:

    涉及到知识点:

    数据类型的检测

    回调函数的使用

    对象、数组的循环

    数组&类数组的特点

    支持返回值

    ……

    1. (function () {
    2. class Drag{
    3. constructor(selector,options){
    4. this.init(selector,options)
    5. }
    6. init(selector,options){
    7. this._selector=document.querySelector(selector);
    8. const defaultOptions={
    9. element:this._selector,
    10. boundary:true,
    11. dragstart:null,
    12. dragmove:null,
    13. dragend:null
    14. }
    15. options=Object.assign(defaultOptions,options)
    16. Drag.each(options,(value,key)=>{
    17. this[`_${key}`]=value;
    18. })
    19. console.log(this)
    20. }
    21. static each(iterator,callback){
    22. const type=Drag.typeof(iterator);
    23. if(type==='Array'){
    24. for(let i=0;i<iterator.length;i++){
    25. callback(iterator[i],i)
    26. }
    27. }else if(type==='Object'){
    28. for(const key in iterator){
    29. if(iterator.hasOwnProperty(key)){
    30. callback(iterator[key],key)
    31. }
    32. }
    33. }
    34. }
    35. static typeof(obj){
    36. return Object.prototype.toString.call(obj).replace(/\[object |\]/g,'')
    37. }
    38. }
    39. window.Drag=Drag;
    40. })()

    拖拽插件封装二:实现具体的功能

    实现拖拽效果

    drag事件:dragover默认行为阻止掉

    call & apply & bind:

    三者区别:

    call&apply立即执行,bind预先改变this,没有立即执行。

    apply传参是数组

    源码

    应用场景:

    dom事件绑定的时候,改变this;

    定时器执行:默认是window。使用bind改变this。之后才做,做的时候this改成我想要的。

    call&apply把函数执行了,改了this。

    在钩子函数中,把一些信息通过参数传递过去

    1. (function () {
    2. class Drag{
    3. constructor(selector,options){
    4. this.init(selector,options)
    5. this._selector.addEventListener('mousedown',this.down.bind(this))
    6. }
    7. init(selector,options){
    8. this._selector=document.querySelector(selector);
    9. const defaultOptions={
    10. element:this._selector,
    11. boundary:true,
    12. dragstart:null,
    13. dragmove:null,
    14. dragend:null
    15. }
    16. options=Object.assign(defaultOptions,options)
    17. Drag.each(options,(value,key)=>{
    18. this[`_${key}`]=value;
    19. })
    20. console.log(this)
    21. }
    22. down(ev){
    23. let {_element}=this;
    24. this.mouseStartX=ev.pageX;
    25. this.mouseStartY=ev.pageY;
    26. this.startX=Number.parseFloat(Drag.queryCss(_element,'left'));
    27. this.startY=Number.parseFloat(Drag.queryCss(_element,'top'));
    28. this._move=this.move.bind(this);
    29. this._up=this.up.bind(this)
    30. document.addEventListener('mousemove',this._move)
    31. document.addEventListener('mouseup',this._up)
    32. this.dragstart && this.dragstart(this,ev);
    33. }
    34. move(ev){
    35. let {mouseStartX,mouseStartY,startX,startY,_element,_boundary}=this;
    36. let curLeft=ev.pageX-mouseStartX+startX;
    37. let curTop=ev.pageY-mouseStartY+startY;
    38. if(_boundary){
    39. // 已约定要相对于父盒子定位
    40. let parent=_element.parentNode;
    41. let minLeft=0,maxLeft=parent.offsetWidth-_element.offsetWidth,minTop=0,maxTop=parent.offsetHeight-_element.offsetHeight;
    42. curLeft=curLeft<minLeft?0:curLeft>maxLeft?maxLeft:curLeft;
    43. curTop=curTop<minTop?0:curTop>maxTop?maxTop:curTop;
    44. }
    45. _element.style.left=`${curLeft}px`;
    46. _element.style.top=`${curTop}px`;
    47. this.dragmove && this.dragmove(this,curLeft,curTop,ev)
    48. }
    49. up(ev){
    50. document.removeEventListener('mousemove',this._move)
    51. document.removeEventListener('mouseup',this._up)
    52. this.dragend && this.dragend(this,ev)
    53. }
    54. static each(iterator,callback){
    55. const type=Drag.typeof(iterator);
    56. if(type==='Array'){
    57. for(let i=0;i<iterator.length;i++){
    58. callback(iterator[i],i)
    59. }
    60. }else if(type==='Object'){
    61. for(const key in iterator){
    62. if(iterator.hasOwnProperty(key)){
    63. callback(iterator[key],key)
    64. }
    65. }
    66. }
    67. }
    68. static typeof(obj){
    69. return Object.prototype.toString.call(obj).replace(/\[object |\]/g,'')
    70. }
    71. static queryCss(elem,attr){
    72. return window.getComputedStyle(elem)[attr];
    73. }
    74. }
    75. window.Drag=Drag;
    76. })()

    插件封装:

    class constructor

    分析配置哪些参数

  • 相关阅读:
    c++ Reference Collapsing
    linux之mail命令发邮件
    easyExcel实现分批导入,动态表头分批导出,以及导出表格样式设置
    puttygen工具ppk文件版本配置
    彻底了解什么是POE交换机!!!
    java计算机毕业设计Internet快递柜管理系统MyBatis+系统+LW文档+源码+调试部署
    每日一题 —— 882. 细分图中的可到达节点
    Vite 是否可以代替 Webpack ?
    1、基本概念
    Three.js教程之在网页快速实现 3D效果(教程含源码)
  • 原文地址:https://blog.csdn.net/betterangela/article/details/127811285