公司项目的上一个版本中,应用内的消息提醒有一个这样的小气泡:
之前这个气泡是固定的,但在新版本的开发中,固定的气泡会阻碍一些按钮的交互,因此跟产品沟通了一下,决定为这个气泡加入沿屏幕右侧纵向拖拽的功能。
在网上找了几个觉得都不太满意。于是结合在网上找到的,尝试自己封装一个公共组件,便于以后类似功能的拓展。
需求分析
怎么使用? 一开始想的是组件式使用,后来,鉴于拖拽都是直接操作DOM 元素,且组件式使用不够灵活,采用了自定义指令的方式。
但还是将两种使用方式都记录下来:
组件式
1 2 3 <v-drag :x-move ="true" > 需拖拽元素 </v-drag >
自定义指令
1 2 3 <div v-drag @click ="fn" > </div > <div v-drag ="options" > </div >
1 2 3 4 5 6 7 8 options:{ XMove: false , YMove: true maxWidth: 300 , maxHeight: 100 , minWidth: 10 , minHeight: 10 }
效果预览
注 :
为方便演示,均监听了鼠标事件,用于移动端时可将鼠标事件的监听注释。
组件式只能设置拖拽方向
遇到的问题及解决 滑动时警告问题 将touch事件绑定给document后,发现拖拽元素时页面也会滚动,并且出现了如下警告
1 2 [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive. See https://www.chromestatus.com/features/5093566007214080
Google了一下后知道,原来移动端chrome出了一个新特性Passive Event Listeners,不会调用 preventDefault 函数来阻止事件的默认行为,这样浏览器就能快速生成事件,从而提升页面性能。
解决办法1: 在touch的事件监听方法上绑定第三个参数{ passive: false }, 通过传递 passive 为 false 来明确告诉浏览器:事件处理程序调用 preventDefault 来阻止默认滑动行为。
1 2 3 4 5 elem.addEventListener( 'touchstart' , fn, { passive : false } );
解决办法2: 1 2 * { touch-action: pan-y; } // 使用全局样式样式去掉
最终代码 组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 <template > <div id ="defaultDragComp" ref ="defaultDragComp" @click.stop ="goNext" @touchstart ="down" > <slot > </slot > </div > </template > <script > export default { name: 'drag-comp' , props: { XMove: { type: Boolean , default : true }, YMove: { type: Boolean , default : true } }, data ( ) { return { defaultDragComp: null , dragging: false , position: {x: 0, y: 0}, maxW: 0, maxH: 0 } }, mounted ( ) { this .initDragComp() }, methods: { initDragComp ( ) { const defaultDragComp = this .$refs.defaultDragComp this .defaultDragComp = defaultDragComp this .maxW = window .innerWidth - defaultDragComp.offsetWidth this .maxH = window .innerHeight - defaultDragComp.offsetHeight }, goNext (done ) { this .$emit('goNext' ) }, down (event ) { document .addEventListener('touchmove' , this .move) document .addEventListener('touchend' , this .end) let {offsetLeft, offsetTop} = this .defaultDragComp this .dragging = true let touch if (event.touches) { touch = event.touches[0] } else { touch = event } let {clientX, clientY} = touch this .position = { x: clientX - offsetLeft, y: clientY - offsetTop } }, move (event ) { let { defaultDragComp, dragging, XMove, YMove, position, maxW, maxH } = this event.preventDefault() if (dragging) { let touch if (event.touches) { touch = event.touches[0] } else { touch = event } let {clientX, clientY} = touch let deltaX = clientX - position.x let deltaY = clientY - position.y if (deltaX < 0) { deltaX = 0 } else if (deltaX > maxW) { deltaX = maxW } if (deltaY < 0) { deltaY = 0 } else if (deltaY > maxH) { deltaY = maxH } if (XMove) { defaultDragComp.style.left = deltaX + 'px' } if (YMove) { defaultDragComp.style.top = deltaY + 'px' } } }, end ( ) { this .dragging = false document .removeEventListener('touchmove' , this .move) document .removeEventListener('touchend' , this .end) } } } </script > <style lang ="less" > #defaultDragComp { position : fixed; z-index : 1010 ; } </style >
自定义指令 drag.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 export default { inserted (el, bindings ) { let dragging = false let position = {x : 0 , y : 0 } el.addEventListener('touchstart' , down, {passive : false }) let options = bindings.value let {XMove, YMove} = options let canXMove = XMove || true let canYMove = YMove || true let {maxW, maxH, minW, minH} = getBoundary(el, options) function getEvent (event ) { let touch if (event.touches) { touch = event.touches[0 ] } else { touch = event } return touch } function down (event ) { document .addEventListener('touchmove' , move, {passive : false }) document .addEventListener('touchend' , end, {passive : false }) let {offsetLeft, offsetTop} = el dragging = true let touch = getEvent(event) let {clientX, clientY} = touch position = { x: clientX - offsetLeft, y: clientY - offsetTop } } function move (event ) { if (dragging) { let touch = getEvent(event) event.preventDefault() let {clientX, clientY} = touch let deltaX = clientX - position.x let deltaY = clientY - position.y if (deltaX < minW) { deltaX = minW } else if (deltaX > maxW) { deltaX = maxW } if (deltaY < minH) { deltaY = minH } else if (deltaY > maxH) { deltaY = maxH } if (canXMove) { el.style.left = deltaX + 'px' } if (canYMove) { el.style.top = deltaY + 'px' } } } function end ( ) { dragging = false document .removeEventListener('touchmove' , move) document .removeEventListener('touchend' , end) } } } function getBoundary (el, options ) { const parentNode = el.offsetParent let {maxWidth, maxHeight, minWidth, minHeight} = options let deltaW = window .innerWidth let deltaH = window .innerHeight let minW = minWidth || 0 let minH = minHeight || 0 if (parentNode) { let {width, height} = parentNode.getBoundingClientRect() deltaW = width deltaH = height } let maxW = (maxWidth || deltaW) - el.offsetWidth let maxH = (maxHeight || deltaH) - el.offsetHeight return { maxW, maxH, minW, minH, } }
main.js
注册全局指令
1 2 3 4 import vDrag from '@/directive/drag' Vue.directive('drag' , vDrag)
使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 <template > <div > <div class ="parent" > <div class ="drag-comp" v-drag ="options" @click.stop ="fn1" > </div > </div > <div class ="drag-comp fixed" v-drag ="options" > fixed</div > </div > </template > <script > export default { data ( ) { return { options: { } }; }, methods: { fn1 ( ) { console .log('click' ); }, } } </script > <style lang ="less" > .parent { position : relative; width : 200px ; height : 300px ; border : 1px solid deepskyblue; } .drag-comp { position : fixed; z-index : 99 ; right : 10px ; bottom : 85px ; width : 40px ; height : 40px ; box-shadow : 0 2px 4px #ddd ; border-radius : 50% ; color : #fff ; font-size : 12px ; display : flex; align-items : center; justify-content : center; background-color : deepskyblue; &.fixed { position : absolute; background : skyblue; } } </style >