微信小程序拖拽組件

自測(cè)兼容蘋果安卓端兼容無(wú)長(zhǎng)按上下跑的bug
碼云地址在文末努释,喜歡的話給個(gè)星敦捧, 拜托啦!胳赌!
效果如圖


darg.png

無(wú)拖拽滑動(dòng)橡皮筋效果問(wèn)題

Component({
    options: {
        multipleSlots: true,
        addGlobalClass: true
    },
    properties: {
        // 數(shù)據(jù)源
        listData: {
            type: Array,
            value: [],
            observer(data) {
                {}
            }
        },
        // 列數(shù)
        columns: {
            type: Number,
            value: 1,
            observer: 'dataChange'
        },
        // 頂部高度
        topSize: {
            type: Number,
            value: 0,
            observer: 'dataChange'
        },
        // 底部高度
        bottomSize: {
            type: Number,
            value: 0,
            observer: 'dataChange'
        }
    },
    data: {
        /* 渲染數(shù)據(jù) */
        windowHeight: 0, // 視窗高度
        platform: '', // 平臺(tái)信息
        realTopSize: 0, // 計(jì)算后頂部高度實(shí)際值
        realBottomSize: 0, // 計(jì)算后底部高度實(shí)際值
        itemDom: {
            // 每一項(xiàng) item 的 dom 信息, 由于大小一樣所以只存儲(chǔ)一個(gè)
            width: 0,
            height: 0,
            left: 0,
            top: 0
        },
        itemWrapDom: {
            // 整個(gè)拖拽區(qū)域的 dom 信息
            width: 0,
            height: 0,
            left: 0,
            top: 0
        },
        startTouch: {
            // 初始觸摸點(diǎn)信息
            pageX: 0,
            pageY: 0,
            identifier: 0
        },

        /* 未渲染數(shù)據(jù) */
        list: [],
        cur: -1, // 當(dāng)前激活的元素
        curZ: -1, // 當(dāng)前激活的元素, 用于控制激活元素z軸顯示
        tranX: 0, // 當(dāng)前激活元素的 X軸 偏移量
        tranY: 0, // 當(dāng)前激活元素的 Y軸 偏移量
        itemWrapHeight: 0, // 動(dòng)態(tài)計(jì)算父級(jí)元素高度
        dragging: false, // 是否在拖拽中
        overOnePage: false, // 整個(gè)區(qū)域是否超過(guò)一個(gè)屏幕
        itemTransition: false, // item 變換是否需要過(guò)渡動(dòng)畫, 首次渲染不需要,
        jialength: 0
        startTranX: 0, // 當(dāng)前激活元素的初始 X軸 偏移量
        startTranY: 0, // 當(dāng)前激活元素的初始 Y軸 偏移量
        preOriginKey: -1, // 前一次排序時(shí)候的起始 key 值

    },
    methods: {
        /**
         * 點(diǎn)擊每一項(xiàng)后觸發(fā)事件
         */
        itemClick(e) {
            let {
                index
            } = e.currentTarget.dataset
            let item = this.data.list[index]
            this.triggerEvent('click', {
                oldKey: index,
                newKey: item.key,
                data: item.data
            })
        },
        /**
         * 長(zhǎng)按觸發(fā)移動(dòng)排序
         */
        longPress(e) {
            // 獲取觸摸點(diǎn)信息
            let startTouch = e.changedTouches[0]
            if (!startTouch) return

            // 如果是固定項(xiàng)則返回
            let index = e.currentTarget.dataset.index
            // 防止多指觸發(fā) drag 動(dòng)作, 如果已經(jīng)在 drag 中則返回, touchstart 事件中有效果
            if (this.data.dragging) return
            this.setData({
                dragging: true
            })

            let {
                pageX: startPageX,
                pageY: startPageY
            } = startTouch, {
                itemDom,
                itemWrapDom
            } = this.data,
                startTranX = 0,
                startTranY = 0

            if (this.data.columns > 1) {
                // 多列的時(shí)候計(jì)算X軸初始位移, 使 item 水平中心移動(dòng)到點(diǎn)擊處
                startTranX = startPageX - itemDom.width / 2 - itemWrapDom.left
            }
            // 計(jì)算Y軸初始位移, 使 item 垂直中心移動(dòng)到點(diǎn)擊處
            startTranY = startPageY - itemDom.height / 2 - itemWrapDom.top

            this.setData({
                startTouch: startTouch,
                startTranX: startTranX,
                startTranY: startTranY,
                cur: index,
                curZ: index,
                tranX: startTranX,
                tranY: startTranY
            })
            wx.vibrateShort()
        },
        touchMove(e) {
            // // 獲取觸摸點(diǎn)信息
            let currentTouch = e.changedTouches[0]
            if (!currentTouch) return
            if (!this.data.dragging) return
            let {
                windowHeight,
                realTopSize,
                realBottomSize,
                itemDom,
                startTouch,
                startTranX,
                startTranY,
                preOriginKey
            } = this.data, {
                pageX: startPageX,
                pageY: startPageY,
                identifier: startId
            } = startTouch, {
                pageX: currentPageX,
                pageY: currentPageY,
                identifier: currentId,
                clientY: currentClientY
            } = currentTouch
            // 如果不是同一個(gè)觸發(fā)點(diǎn)則返回
            if (startId !== currentId) return
            // 通過(guò) 當(dāng)前坐標(biāo)點(diǎn), 初始坐標(biāo)點(diǎn), 初始偏移量 來(lái)計(jì)算當(dāng)前偏移量
            let tranX = currentPageX - startPageX + startTranX,
                tranY = currentPageY - startPageY + startTranY
            // 單列時(shí)候X軸初始不做位移
            if (this.data.columns === 1) tranX = 0
            // 判斷是否超過(guò)一屏幕, 超過(guò)則需要判斷當(dāng)前位置動(dòng)態(tài)滾動(dòng)page的位置
            if (this.data.overOnePage) {
                if (currentClientY > windowHeight - itemDom.height - realBottomSize) {
                    // 當(dāng)前觸摸點(diǎn)pageY + item高度 - (屏幕高度 - 底部固定區(qū)域高度)
                    wx.pageScrollTo({
                        scrollTop: currentPageY + itemDom.height - (windowHeight - realBottomSize),
                        duration: 300
                    })
                } else if (currentClientY < itemDom.height + realTopSize) {
                    // 當(dāng)前觸摸點(diǎn)pageY - item高度 - 頂部固定區(qū)域高度
                    wx.pageScrollTo({
                        scrollTop: currentPageY - itemDom.height - realTopSize,
                        duration: 300
                    })
                }
            }
            // // 設(shè)置當(dāng)前激活元素偏移量
            this.setData({
                tranX: tranX,
                tranY: tranY
            })
        },
        touchEnd(e) {
            if (!this.data.dragging) return
            // 獲取觸摸點(diǎn)信息
            let currentTouch = e.changedTouches[0]
            if (!currentTouch) return

            if (!this.data.dragging) return

            let {
                windowHeight,
                realTopSize,
                realBottomSize,
                itemDom,
                startTouch,
                startTranX,
                startTranY,
                preOriginKey
            } = this.data, {
                pageX: startPageX,
                pageY: startPageY,
                identifier: startId
            } = startTouch, {
                pageX: currentPageX,
                pageY: currentPageY,
                identifier: currentId,
                clientY: currentClientY
            } = currentTouch

            // 如果不是同一個(gè)觸發(fā)點(diǎn)則返回
            if (startId !== currentId) return

            // 通過(guò) 當(dāng)前坐標(biāo)點(diǎn), 初始坐標(biāo)點(diǎn), 初始偏移量 來(lái)計(jì)算當(dāng)前偏移量
            let tranX = currentPageX - startPageX + startTranX,
                tranY = currentPageY - startPageY + startTranY

            // 單列時(shí)候X軸初始不做位移
            if (this.data.columns === 1) tranX = 0

            // 獲取 originKey 和 endKey
            let originKey = parseInt(e.currentTarget.dataset.key),
                endKey = this.calculateMoving(tranX, tranY)

            // 防止拖拽過(guò)程中發(fā)生亂序問(wèn)題
            if (originKey === endKey || preOriginKey === originKey) return
            this.setData({
                preOriginKey: originKey
            })

            this.insert(originKey, endKey)
            this.clearData()
        },
        /**
         * 根據(jù)當(dāng)前的手指偏移量計(jì)算目標(biāo)key
         */
        calculateMoving(tranX, tranY) {
            let {
                itemDom
            } = this.data

            let rows = Math.ceil(this.data.list.length / this.data.columns) - 1,
                i = Math.round(tranX / itemDom.width),
                j = Math.round(tranY / itemDom.height)

            i = i > this.data.columns - 1 ? this.data.columns - 1 : i
            i = i < 0 ? 0 : i
            j = j < 0 ? 0 : j
            j = j > rows ? rows : j

            let endKey = i + this.data.columns * j
            endKey =
                endKey >= this.data.list.length ? this.data.list.length - 1 : endKey

            return endKey
        },
        /**
         * 根據(jù)起始key和目標(biāo)key去重新計(jì)算每一項(xiàng)的新的key
         */

        // 交換數(shù)組位置
        swapArr(arr, index1, index2) {
            arr[index1] = arr.splice(index2, 1, arr[index1])[0]
            return arr
        },
        insert(origin, end) {
            this.setData({
                itemTransition: true
            })

            console.log('origin', origin)
            console.log('end', end)
            let list
            if (origin < end) {
                // 正序拖動(dòng)
                list = this.swapArr(this.data.list, origin, end)
                this.del(list)
                console.log('正序拖動(dòng)', list)
                this.getPosition(list)
            } else if (origin > end) {
                // 倒序拖動(dòng)
                list = this.swapArr(this.data.list, end, origin)
                this.del(list)
                console.log('倒序拖動(dòng)', list)
                // this.del()
                this.getPosition(list)
            }
        },
        del(list) {
            for (let i = 0; i < list.length; i = i + 2) {
                if (list[i].data.nickName == '' && list[i + 1].data.nickName == '') {
                    list.splice(i, 2)
                }
            }
            return list
        },
        /**
         * 正序拖動(dòng) key 值和固定項(xiàng)判斷邏輯
         */
        l2r(key, origin) {
            if (key === origin) return origin
            if (this.data.list[key].fixed) {
                return this.l2r(key - 1, origin)
            } else {
                return key
            }
        },
        /**
         * 倒序拖動(dòng) key 值和固定項(xiàng)判斷邏輯
         */
        r2l(key, origin) {
            if (key === origin) return origin
            if (this.data.list[key].fixed) {
                return this.r2l(key + 1, origin)
            } else {
                return key
            }
        },

        /**
         * 根據(jù)排序后 list 數(shù)據(jù)進(jìn)行位移計(jì)算
         */
        getPosition(data, vibrate = true) {
            let that = this
            let temp = []
            let {
                platform,
                itemDom
            } = this.data
            let list = data.map((item, index) => {
                item.tranX = itemDom.width * (index % this.data.columns)
                item.tranY = Math.floor(index / this.data.columns) * itemDom.height
                return item
            })
            this.setData({
                list: list
            })
            if (!vibrate) return
            if (platform !== 'devtools') wx.vibrateShort()
            console.log('list', list);
            list.map((item, index) => {
                temp[index] = item.data
                temp[index].tranX = item.tranX
                temp[index].tranY = item.tranY
            })
            // 傳出的數(shù)據(jù)
            let jialength = Math.floor(temp.length / 2)
            console.log('加號(hào)長(zhǎng)度', jialength);
            this.setData({
                jialength: jialength
            })
            this.triggerEvent('change', {
                listData: temp,
            })
        },
        /**
         * 清除參數(shù)
         */
        clearData() {
            this.setData({
                preOriginKey: -1,
                dragging: false,
                cur: -1,
                tranX: 0,
                tranY: 0
            })
            // 延遲清空
            setTimeout(() => {
                this.setData({
                    curZ: -1
                })
            }, 300)
        },
        /**
         *  初始化獲取 dom 信息
         */
        initDom() {
            wx.pageScrollTo({
                scrollTop: 0,
                duration: 0
            })
            let {
                windowWidth,
                windowHeight,
                platform
            } = wx.getSystemInfoSync()
            let remScale = (windowWidth || 375) / 375,
                realTopSize = (this.data.topSize * remScale) / 2,
                realBottomSize = (this.data.bottomSize * remScale) / 2

            this.setData({
                windowHeight: windowHeight,
                platform: platform,
                realTopSize: realTopSize,
                realBottomSize: realBottomSize
            })

            this.createSelectorQuery()
                .select('.item')
                .boundingClientRect(res => {
                    let rows = Math.ceil(this.data.list.length / this.data.columns)

                    this.setData({
                        itemDom: res,
                        itemWrapHeight: rows * res.height
                    })

                    this.getPosition(this.data.list, false)

                    this.createSelectorQuery()
                        .select('.item-wrap')
                        .boundingClientRect(res => {
                            // (列表的底部到頁(yè)面頂部距離 > 屏幕高度 - 底部固定區(qū)域高度) 用該公式來(lái)計(jì)算是否超過(guò)一頁(yè)
                            let overOnePage = res.bottom > windowHeight - realBottomSize

                            this.setData({
                                itemWrapDom: res,
                                overOnePage: overOnePage
                            })
                            console.log('overOnePage'.overOnePage);
                        })
                        .exec()
                })
                .exec()
        },
        /**
         *  初始化
         */
        init() {
            this.clearData()
            // 避免獲取不到節(jié)點(diǎn)信息報(bào)錯(cuò)問(wèn)題
            if (this.data.listData.length === 0) {
                this.setData({
                    list: []
                })
                return
            }
            // 遍歷數(shù)據(jù)源增加擴(kuò)展項(xiàng), 以用作排序使用

            let list = this.data.listData.map((item, index) => {
                return {
                    key: index,
                    tranX: 0,
                    tranY: 0,
                    data: item
                }
            })
            this.setData({
                list: list,
                jialength: list.length / 2,
                itemTransition: false
            })
            
            setTimeout(() => this.initDom(), 50)
        }
    },
    ready() {
        this.init()
    }
})

gitee 碼云地址 https://gitee.com/xiamengmeng/wx-ui.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咳榜,一起剝皮案震驚了整個(gè)濱河市潘懊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贿衍,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件救恨,死亡現(xiàn)場(chǎng)離奇詭異贸辈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門擎淤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)奢啥,“玉大人,你說(shuō)我怎么就攤上這事嘴拢∽ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵席吴,是天一觀的道長(zhǎng)赌结。 經(jīng)常有香客問(wèn)我,道長(zhǎng)孝冒,這世上最難降的妖魔是什么柬姚? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮庄涡,結(jié)果婚禮上量承,老公的妹妹穿的比我還像新娘。我一直安慰自己穴店,他們只是感情好撕捍,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泣洞,像睡著了一般忧风。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斜棚,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天阀蒂,我揣著相機(jī)與錄音,去河邊找鬼弟蚀。 笑死蚤霞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的义钉。 我是一名探鬼主播昧绣,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼捶闸!你這毒婦竟也來(lái)了夜畴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤删壮,失蹤者是張志新(化名)和其女友劉穎贪绘,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體央碟,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡税灌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片菱涤。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡苞也,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出粘秆,到底是詐尸還是另有隱情如迟,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布攻走,位于F島的核電站殷勘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏陋气。R本人自食惡果不足惜劳吠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望巩趁。 院中可真熱鬧痒玩,春花似錦、人聲如沸议慰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)别凹。三九已至草讶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炉菲,已是汗流浹背堕战。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拍霜,地道東北人嘱丢。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像祠饺,于是被迫代替她去往敵國(guó)和親越驻。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359