小程序拖拽排序

<!-- 页面拖拽列表 -->
<text class="flex-center">拖拽列表排序</text>

<!-- HACK: 这里使用catch,是为了避免拖拽列表的时候页面滑动 -->
<movable-area
    class="movable-container"
    style="height: {{ elementHeight * list.length }}px;"
    catch:touchmove="onHackTouchMove"
>
    <block wx:for="{{ list }}" wx:key="index">
        <view
            class="flex-center item {{ dragElement && dragElement.id === item.id ? 'item--tran' : '' }} {{ lastTarget === index ? 'item--target' : '' }}"
            style='height: {{ elementHeight }}px;'
            data-index="{{ index }}"
            bind:longpress="onLongPress"
            bind:touchmove="onTouchMove"
            bind:touchend="onTouchEnd"
        >{{ item.content }}</view>
        <!-- <view
            class="flex-center item"
            style='height:80px;'
            data-index="{{ index }}"
            bind:longpress="onLongPress"
            bind:touchmove="onTouchMove"
            bind:touchend="onTouchEnd"
        >{{ item.content }}</view> -->
    </block>

    <!-- HACK: animation设置为false,避免开始显示此组件的滑动效果 -->
    <movable-view
        hidden="{{ !dragElement }}"
        class="flex-center item"
        style='height: {{ elementHeight }}px;'
        direction="vertical"
        disabled
        animation="{{ false }}"
        y="{{ movableViewY }}"
    >{{ dragElement.content }}</movable-view>
</movable-area>
/* 居中 */
.flex-center {
    display: flex;
    align-items: center;
    justify-content: center;
}

.movable-container {
    width: 100%;
    border: 2rpx solid cadetblue;
}
    .item {
        width: 100%;
        border: 1px solid salmon;
        font-size: 100rpx;
        background-color: #fff;
    }
        .item--tran {
            background-color: #ddd;
        }
        .item--target {
            background-color: lightseagreen;
        }
Page({
    data: {


        /** 列表 */
        list: [{
            id: 1,
            content: 1
        }, {
            id: 2,
            content: 2
        }, {
            id: 3,
            content: 3
        }, {
            id: 4,
            content: 4
        }, {
            id: 5,
            content: 5
        }, {
            id: 6,
            content: 6
        }, {
            id: 7,
            content: 7
        }, {
            id: 8,
            content: 8
        }, {
            id: 9,
            content: 9
        }, {
            id: 10,
            content: 10
        }, {
            id: 11,
            content: 11
        }, {
            id: 12,
            content: 12
        }],

        /** 单一项高度 */
        elementHeight: 80,

        /** 滑动项 */
        dragElement: null,
        /** movable-view组件y轴坐标,滑动时滑动项左上角距离文档顶部纵坐标,单位px */
        movableViewY: null,
        /** 滑动过程中经过的项 */
        lastTarget: null,
                //#region 纯数据字段
        /** 屏幕高度,单位px */
        _windowHeight: null,
        /** 开始触摸时的单一项左上角y坐标 */
        _startOffsetY: null,
        /** 开始触摸位置y坐标 */
        _startPageY: null,
        /** 开始触摸项索引 */
        _startDragElementIndex: null,

        /** 滑动偏移 */
        _scrollThreshold: 0.5,

        /** 距顶部/左边多远时,触发 _scrollToUpper 事件,单位px,即上滑至屏幕顶部 */
        _upperThreshold: 100,
        /** 距底部/右边多远时,触发 _scrollToLower 事件,单位px,即下滑至屏幕底部 */
        _lowerThreshold: 100,
        /** 上滑和下滑时间,单位毫秒 */
        _scrollDuration: 1000,
        //#endregion
    },

    /** 生命周期函数--监听页面展示 */
    onShow() {
        this._getWindowHeight();
    },
    /** 长按触发事件 */
/** */

    onLongPress(event) {
        console.log('[onLongPress]', event);

        let dragElementIndex = event.currentTarget.dataset.index;
        let dragElement = this.data.list[dragElementIndex];
        this.setData({
            /** 点击项左上角y坐标 */
            _startOffsetY: event.target.offsetTop,
            /** 点击位置y坐标 */
            _startPageY: event.touches[0].pageY,
            /** 点击项索引 */
            _startDragElementIndex: dragElementIndex,
            /** 点击项 */
            dragElement,
            /** movable-view组件左上角y坐标 */
            movableViewY: event.target.offsetTop
        });
    },
/**
 * 
 * @param {*} event 
 */
    /**
     * 手指触摸后移动
     * - 触底或触顶时下滑或者上滑
     * - 移动movable-view
     */
    onTouchMove(event) {
        console.log('[onTouchMove]', event);

        // 长按事件
        if (this.data.dragElement) {
            /** 触摸点位置在显示屏幕区域左上角Y坐标 */
            let clientY = event.touches[0].clientY;        
            /** 触摸点位置距离文档左上角Y坐标 */
            let pageY = event.touches[0].pageY;
            /** 和最初点击位置比较移动距离 */
            let targetMoveDistance = pageY - this.data._startPageY;
            /** 移动后的movable-view组件位置 */
            let movableViewY = this.data._startOffsetY + targetMoveDistance;
            /** 经过项索引 */
            let targetIndex = this._computeFutureIndex(targetMoveDistance, this.data._startDragElementIndex);

            this._pageScroll(clientY, pageY);

            this.setData({
                movableViewY,
                lastTarget: targetIndex
            });
        }
    },
    /** 滑动结束 */
    onTouchEnd(event) {
        console.log('[onTouchEnd]', event);

        if (this.data.dragElement) {
            let list = this._deepCopy(this.data.list);
            /** 结束点位置y坐标 */
            let pageY = event.changedTouches[0].pageY;
            /** 和初始点击位置比较移动距离 */
            let targetMoveDistance = pageY - this.data._startPageY;
            /** 初始点击项索引 */
            let dragElementIndex = this.data._startDragElementIndex;

            /** 目标项索引 */
            const futureIndex = this._computeFutureIndex(targetMoveDistance, dragElementIndex);
            if (futureIndex !== false) {
                list.splice(futureIndex, 0, list.splice(dragElementIndex, 1)[0]);  // 移动位置
            }

            this.setData({
                list,
                dragElement: null,
                lastTarget: null
            });
        }
    },
    /** 阻止滑动 */
    onHackTouchMove() { },
    /** 获取可使用窗口高度,单位px */
    _getWindowHeight() {
        try {
            const { windowHeight } = wx.getSystemInfoSync();
            this.setData({
                _windowHeight: windowHeight
            });
        } catch (err) {
            console.error('[_getWindowHeight]', err);
        }
    },
    /** 页面滑动 */
    _pageScroll(clientY, pageY) {
        if (clientY + this.data._upperThreshold >= this.data._windowHeight) {
            // 下滑接近屏幕底部
            wx.pageScrollTo({
                scrollTop: pageY + this.data.elementHeight,
                duration: this.data._scrollDuration
            });
        } else if (clientY - this.data._lowerThreshold <= 0) {
            // 上滑接近屏幕顶部
            wx.pageScrollTo({
                scrollTop: pageY - this.data.elementHeight,
                duration: this.data._scrollDuration
            })
        }
    },
    /**
     * 计算目标索引
     * @param {number} targetMoveDistance 移动距离
     * @param {number} dtagElementIndex 初始移动项索引
     * 若轻轻拂动则返回false
     */
    _computeFutureIndex(targetMoveDistance, dragElementIndex) {
        let willInsertAfter = this._getSwapDirection(targetMoveDistance);
        if (willInsertAfter !== false) {            
            /** 偏移索引 */
            let offsetElementIndex = dragElementIndex + willInsertAfter;
            /** 移动步数 */
            let step = targetMoveDistance / this.data.elementHeight;
            /** 步数补偿,当只有移动距离超过单项 _scrollThreshold 时才算有效 */
            if (step <= -1) {
                step += this.data._scrollThreshold;
            } else if (step >= 1) {
                step -= this.data._scrollThreshold;
            }
            /** 目标索引 */
            let futureIndex = parseInt(step) + offsetElementIndex;            

            // 避免越界
            if (futureIndex < 0) {
                futureIndex = 0;
            } else if (futureIndex > this.data.list.length - 1) {
                futureIndex = this.data.list.length - 1;
            }

            return futureIndex;
        } else {
            return willInsertAfter;
        }
    },
    /**
     * 获取滑动方向
     * @param {number} targetMoveDistance 移动距离
     * @returns {number/boolean}
     *  - 1 下滑
     *  - -1 上滑
     *  - false 拂动,滑动距离小于一半单项高度
     */
    _getSwapDirection(targetMoveDistance) {
        if (Math.abs(targetMoveDistance) < this.data.elementHeight / 2) {
            // 轻轻拂动,滑动距离小于1/2单项高度
            return false;
        } else if (targetMoveDistance >= this.data.elementHeight / 2) {
            console.log('[_getSwapDirection] 👇👇👇');
            return 1;  // 下滑
        } else if (targetMoveDistance <= this.data.elementHeight / -2) {
            console.log('[_getSwapDirection] 👆👆👆');
            return -1;  // 上滑
        }
    },
    /** 深拷贝 */
    _deepCopy(obj) {
        // 只拷贝对象
        if (typeof obj !== 'object') return;
        // 根据obj的类型判断是新建一个数组还是一个对象
        var newObj = obj instanceof Array ? [] : {};
        for (var key in obj) {
            // 遍历obj,并且判断是obj的属性才拷贝
            if (obj.hasOwnProperty(key)) {
                // 判断属性值的类型,如果是对象递归调用深拷贝
                newObj[key] = typeof obj[key] === 'object' ? this._deepCopy(obj[key]) : obj[key];
            }
        }
        return newObj;
    }
})


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值