UNPKG

wsemi

Version:

A support package for web developer.

333 lines (279 loc) 13.6 kB
import get from 'lodash/get' import isBoolean from 'lodash/isBoolean' import isFunction from 'lodash/isFunction' import evem from './evem.mjs' import domCancelEvent from './domCancelEvent.mjs' /** * 前端監聽指定panel與bar的DOM元素,可偵測滾輪與拖曳bar事件 * * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/domDragBarAndScroll.test.mjs Github} * @memberOf wsemi * @param {HTMLElement} panel 輸入panel元素 * @param {HTMLElement} bar 輸入bar元素 * @param {Object} [opt={}] 輸入設定物件,預設{} * @param {Function} [getHeighRatio=()=>1] 輸入取得高度比例函數,因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前heighRatio,預設()=>1 * @param {Function} [getWidthRatio=()=>1] 輸入取得寬度比例函數,因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前widthRatio,預設()=>1 * @param {Boolean} [stopScrollPropagationForPanel=false] 輸入是否停用滑鼠捲動事件向上傳遞布林值,預設false * @param {Boolean} [stopTouchDragPropagationForPanel=false] 輸入是否停用手機拖曳事件向上傳遞布林值,預設false * @param {Boolean} [useTouchDragForPanel=true] 輸入是否使用手機拖曳事件布林值,預設true * @returns {Object} 回傳物件,可使用on與clear函數,on可監聽pressBar、dragBar、freeBar事件,clear為釋放監聽 * @example * need test in browser * * //監聽dom * let divPanel = document.querySelector('#id_panel') * let divBar = document.querySelector('#id_bar') * let getHeighRatio = () => 0.9 * let das = domDragBarAndScroll(divPanel, divBar, { getHeighRatio, stopScrollPropagationForPanel: true, stopTouchDragPropagationForPanel: true }) * das.on('scrollPanel', () => {}) * das.on('pressBar', () => {}) * das.on('dragBar', () => {}) * das.on('freeBar', () => {}) * * //釋放監聽 * das.clear() * */ function domDragBarAndScroll(panel, bar, opt = {}) { let fPanelWheel = null let fPanelTouchstart = null let fPanelTouchmove = null let fPanelTouchend = null let fBarMousedown = null let fBarMouseup = null let fBarTouchstart = null let fBarTouchmove = null let fBarTouchend = null let fWindowMousemove = null let fWindowMouseup = null let fWindowScroll = null let fWindowWheel = null let fDocumentScroll = null let fDocumentWheel = null let barPressing = false //getHeighRatio, 因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前heighRatio let getHeighRatio = get(opt, 'getHeighRatio', null) if (!isFunction(getHeighRatio)) { getHeighRatio = () => 1 } //getWidthRatio, 因組件本身或內容物可能會調整尺寸, 故需由外部給予函數取得當前widthRatio let getWidthRatio = get(opt, 'getWidthRatio', null) if (!isFunction(getWidthRatio)) { getWidthRatio = () => 1 } //stopScrollPropagationForPanel let stopScrollPropagationForPanel = get(opt, 'stopScrollPropagationForPanel', null) if (!isBoolean(stopScrollPropagationForPanel)) { stopScrollPropagationForPanel = false } //stopTouchDragPropagationForPanel let stopTouchDragPropagationForPanel = get(opt, 'stopTouchDragPropagationForPanel', null) if (!isBoolean(stopTouchDragPropagationForPanel)) { stopTouchDragPropagationForPanel = false } //useTouchDragForPanel let useTouchDragForPanel = get(opt, 'useTouchDragForPanel', null) if (!isBoolean(useTouchDragForPanel)) { useTouchDragForPanel = true } //ele let elePanel = panel let eleBar = bar //ev let ev = evem() function add() { //fPanelWheel fPanelWheel = (e) => { let ry = e.deltaY / Math.abs(e.deltaY) let rx = e.deltaX / Math.abs(e.deltaX) ev.emit('scrollPanel', { ratioY: ry, ratioX: rx }) //寬版頁面, 用滾輪上下捲動, 實際是傳移動距離給bar if (stopScrollPropagationForPanel) { domCancelEvent(e) //要禁止外部元素如body被滑鼠捲動 } } elePanel.addEventListener('wheel', fPanelWheel) //fPanelTouchstart fPanelTouchstart = (e) => { if (useTouchDragForPanel) { //acy, acx let acy = get(e, `touches.0.clientY`, 0) let acx = get(e, `touches.0.clientX`, 0) //cy, cx let cy = -acy * getHeighRatio() let cx = -acx * getWidthRatio() //barPressing barPressing = true //emit ev.emit('pressBar', { clientY: cy, clientX: cx }) //窄版頁面, 上鎖與紀錄頁面點擊y座標, 僅紀錄第一觸擊點座標, 另需被heighRatio修正比例 //cancel if (stopTouchDragPropagationForPanel) { domCancelEvent(e) } } } elePanel.addEventListener('touchstart', fPanelTouchstart) //fPanelTouchmove fPanelTouchmove = (e) => { if (useTouchDragForPanel && barPressing) { //acy, acx let acy = get(e, `touches.0.clientY`, 0) let acx = get(e, `touches.0.clientX`, 0) // //check, 若水平向分量滑動較多, 則清空垂直向移動並結束 // if (acx > 0 && acy > 0 && Math.abs(acx) >= Math.abs(acy)) { // e.touches[0].clientY = 0 //Cannot set property clientY of #<Touch> which has only a getter // return // } //cy, cx let cy = -acy * getHeighRatio() let cx = -acx * getWidthRatio() //emit ev.emit('dragBar', { clientY: cy, clientX: cx }) //窄版頁面, 用滑動距離拖曳頁面, 實際是傳移動距離給bar, 僅紀錄第一觸擊點座標, 另需被heighRatio修正比例 //cancel if (stopTouchDragPropagationForPanel) { domCancelEvent(e) //要禁止外部元素如body被拖曳移動畫面, 此會導致無法左右拖曳元素(導致開啟overflow-x:auto無法使用) } } } elePanel.addEventListener('touchmove', fPanelTouchmove, { passive: false }) //必須使用passive=false否則無法cancel //fPanelTouchend fPanelTouchend = (e) => { if (useTouchDragForPanel && barPressing) { //barPressing barPressing = false //emit ev.emit('freeBar') //窄版頁面, 解鎖 //cancel if (stopTouchDragPropagationForPanel) { domCancelEvent(e) } } } elePanel.addEventListener('touchend', fPanelTouchend) //fBarMousedown fBarMousedown = (e) => { barPressing = true ev.emit('pressBar', { clientY: e.clientY, clientX: e.clientX }) //寬版bar, 上鎖與紀錄點擊y座標 //domCancelEvent(e) } eleBar.addEventListener('mousedown', fBarMousedown) //fBarMouseup, 可由fWindowMouseup自動解鎖, 不過若嵌入panel有攔截mouseup事件(例如popup)會導致外面window收不到mouseup事件, 故bar的mouseup事件仍需要監聽處理解鎖行為 fBarMouseup = (e) => { if (barPressing) { barPressing = false ev.emit('freeBar') //窄版bar, 解鎖 //domCancelEvent(e) } } eleBar.addEventListener('mouseup', fBarMouseup) //fBarTouchstart fBarTouchstart = (e) => { barPressing = true ev.emit('pressBar', { clientY: e.touches[0].clientY, clientX: e.touches[0].clientX }) //窄版bar, 上鎖與紀錄bar點擊y座標 //domCancelEvent(e) } eleBar.addEventListener('touchstart', fBarTouchstart) //fBarTouchmove fBarTouchmove = (e) => { if (barPressing) { ev.emit('dragBar', { clientY: e.touches[0].clientY, clientX: e.touches[0].clientX }) //窄版bar, 用滑動距離拖曳bar, 實際是傳移動距離給bar domCancelEvent(e) //要禁止外部元素如body被拖曳移動畫面 } } eleBar.addEventListener('touchmove', fBarTouchmove, { passive: false }) //必須使用passive=false否則無法cancel //fBarTouchend fBarTouchend = (e) => { if (barPressing) { barPressing = false ev.emit('freeBar') //窄版bar, 解鎖 //domCancelEvent(e) } } eleBar.addEventListener('touchend', fBarTouchend) //fWindowMousemove fWindowMousemove = (e) => { if (barPressing) { ev.emit('dragBar', { clientY: e.clientY, clientX: e.clientX }) //寬版bar, 用鎖與滑動距離拖曳bar //domCancelEvent(e) } } window.addEventListener('mousemove', fWindowMousemove) //fWindowMouseup fWindowMouseup = (e) => { if (barPressing) { barPressing = false ev.emit('freeBar') //寬版bar, 解鎖 if (stopScrollPropagationForPanel) { domCancelEvent(e) //要禁止回傳否則會連外部body捲軸一起移動畫面 } } } window.addEventListener('mouseup', fWindowMouseup) //fWindowScroll fWindowScroll = (e) => { // if (barPressing) { // console.log('cancel window scroll') // //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件 // //此事件無法取消(cancelable=false), 即便使用passive=false // domCancelEvent(e) // } } window.addEventListener('scroll', fWindowScroll, { passive: false }) //fWindowWheel fWindowWheel = (e) => { // if (barPressing) { // console.log('cancel window wheel') // //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件 // //此事件無法取消(cancelable=false), 即便使用passive=false // domCancelEvent(e) // } } window.addEventListener('wheel', fWindowWheel, { passive: false }) //fDocumentScroll fDocumentScroll = (e) => { // if (barPressing) { // console.log('cancel document scroll') // //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件 // //此事件無法取消(cancelable=false), 即便使用passive=false // domCancelEvent(e) // } } document.addEventListener('scroll', fDocumentScroll, { passive: false }) //fDocumentWheel fDocumentWheel = (e) => { // if (barPressing) { // console.log('cancel document wheel') // //若已經為滑鼠捲動或觸控拖曳時, 則取消傳遞至外部的scroll事件, 避免例如手機拖曳觸底時會另外呼叫window的scroll事件 // //此事件無法取消(cancelable=false), 即便使用passive=false // domCancelEvent(e) // } } document.addEventListener('wheel', fDocumentWheel, { passive: false }) } function clear() { //elePanel remove wheel, touchstart, touchmove, touchend elePanel.removeEventListener('wheel', fPanelWheel) elePanel.removeEventListener('touchstart', fPanelTouchstart) elePanel.removeEventListener('touchmove', fPanelTouchmove) elePanel.removeEventListener('touchend', fPanelTouchend) //eleBar remove mousedown, mouseup, touchstart, touchmove, touchend eleBar.removeEventListener('mousedown', fBarMousedown) eleBar.removeEventListener('mouseup', fBarMouseup) eleBar.removeEventListener('touchstart', fBarTouchstart) eleBar.removeEventListener('touchmove', fBarTouchmove) eleBar.removeEventListener('touchend', fBarTouchend) //window remove mousemove, mouseup, wheel window.removeEventListener('mousemove', fWindowMousemove) window.removeEventListener('mouseup', fWindowMouseup) window.removeEventListener('scroll', fWindowScroll) window.removeEventListener('wheel', fWindowWheel) //document remove scroll, wheel document.removeEventListener('scroll', fDocumentScroll) document.removeEventListener('wheel', fDocumentWheel) } //add add() //save ev.clear = clear return ev } export default domDragBarAndScroll