123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- <template>
- <scroll-view class="m-drag" scroll-y :style="{ height: itemHeight * state.newList.length + 'px' }">
- <view
- v-for="(item, index) in state.newList"
- :key="index"
- class="m-drag-item"
- :class="{ active: state.currentIndex === index }"
- :style="{ top: state.itemYList[index].top + 'px' }">
- <slot v-bind="{item, index}"/>
- <view class="icon" @touchstart="touchStart($event, index)" @touchmove="touchMove" @touchend="touchEnd">
- <i class="lines"/>
- </view>
- </view>
- </scroll-view>
- </template>
- <script setup>
- import {reactive, watch, toRefs} from 'vue';
- const emits = defineEmits(['change'])
- const props = defineProps({
- // 每一项item高度
- itemHeight: {
- type: Number,
- required: true
- },
- // 数据列表
- list: {
- type: Array,
- required: true
- },
- // 是否只读
- readonly: {
- type: Boolean,
- default: false
- }
- })
- const state = reactive({
- // 数据
- newList: [],
- // 记录所有item的初始坐标
- initialItemYList: [],
- // 坐标数据
- itemYList: [],
- // 记录当前手指的垂直方向的坐标
- touchY: 0,
- // 记录当前操作的item数据
- currentItemY: {},
- // 当前操作的item的下标
- currentIndex: -1,
- // 正在拖拽
- dragging: false
- })
- watch(
- () => props.list,
- (val) => {
- if (!val?.length) return
- // 获取数据列表
- state.newList = [...val] // copy array
- // 获取所有item的初始坐标
- state.initialItemYList = getItemsY()
- // 初始化坐标
- state.itemYList = getItemsY()
- },
- {
- immediate: true
- }
- )
- /** @初始化各个item的坐标 **/
- function getItemsY() {
- return props.list.map((item, i) => {
- return {
- left: 0,
- top: i * props.itemHeight
- }
- })
- }
- /** @开始触摸 */
- function touchStart(event, index) {
- // 只读
- if (props.readonly) return
- // H5拖拽时,禁止触发ios回弹
- h5BodyScroll(false)
- const [{pageY}] = event.touches
- // 记录数据
- state.dragging = true
- state.currentIndex = index
- state.touchY = pageY
- state.currentItemY = state.itemYList[index]
- }
- /** @手指滑动 **/
- function touchMove(event) {
- // 只读
- if (props.readonly) return
- const [{pageY}] = event.touches
- const current = state.itemYList[state.currentIndex]
- const prep = state.itemYList[state.currentIndex - 1]
- const next = state.itemYList[state.currentIndex + 1]
- // 获取移动差值
- state.itemYList[state.currentIndex] = {
- top: current.top + (pageY - state.touchY)
- }
- // 记录手指坐标
- state.touchY = pageY
- // 向下移动(超过下一个的1/2就进行换位)
- if (next && current.top > next.top - props.itemHeight / 2) {
- changePosition(state.currentIndex + 1)
- } else if (prep && current.top < prep.top + props.itemHeight / 2) {
- // 向上移动(超过上一个的1/2就进行换位)
- changePosition(state.currentIndex - 1)
- }
- }
- /** @手指松开 */
- function touchEnd() {
- // 只读
- if (props.readonly) return
- // 传给父组件新数据
- emits('change', state.newList, props.list, state.newList[state.currentIndex])
- // 将拖拽的item归位
- state.itemYList[state.currentIndex] = state.initialItemYList[state.currentIndex]
- state.currentIndex = -1
- // H5开启ios回弹
- h5BodyScroll(true)
- state.dragging = false
- }
- /** @交换位置 **/
- // index 需要与第几个下标交换位置
- function changePosition(index) {
- console.log(index)
- // 记录当前拖拽的item数据
- const tempItem = state.newList[state.currentIndex]
- // 设置原来位置的item
- state.newList[state.currentIndex] = state.newList[index]
- // 将临时存放的数据设置好
- state.newList[index] = tempItem
- // 调整位置item
- state.itemYList[index] = state.itemYList[state.currentIndex]
- state.itemYList[state.currentIndex] = state.currentItemY
- // 改变当前操作的的下标
- state.currentIndex = index
- // 记录新位置的数据
- state.currentItemY = state.initialItemYList[state.currentIndex]
- }
- // h5 ios回弹
- function h5BodyScroll(flag) {
- // #ifdef H5
- document.body.style.overflow = flag ? 'initial' : 'hidden'
- // #endif
- }
- defineExpose({...toRefs(state)})
- </script>
- <style scoped lang="scss">
- .m-drag {
- position: relative;
- width: 100%;
- ::-webkit-scrollbar {
- display: none;
- }
- .m-drag-item {
- position: absolute;
- left: 0;
- right: 0;
- transition: all ease 0.25s;
- display: flex;
- align-items: center;
- > :deep(view:not(.icon)) {
- flex: 1;
- }
- .icon {
- padding: 30rpx;
- .lines {
- background: #e0e0e0;
- width: 20px;
- height: 2px;
- border-radius: 100rpx;
- margin-left: auto;
- position: relative;
- display: block;
- transition: all ease 0.25s;
- &::before,
- &::after {
- position: absolute;
- width: inherit;
- height: inherit;
- border-radius: inherit;
- background: #e0e0e0;
- transition: inherit;
- content: '';
- display: block;
- }
- &::before {
- top: -14rpx;
- }
- &::after {
- bottom: -14rpx;
- }
- }
- }
- // 拖拽中的元素,添加阴影、关闭动画、层级置顶
- &.active {
- box-shadow: 0 0 14rpx rgba(0, 0, 0, 0.08);
- transition: initial;
- z-index: 1;
- .icon .lines {
- background: #2e97f9;
- &::before,
- &::after {
- background: #2e97f9;
- }
- }
- }
- }
- }
- </style>
|