uniapp+vue move-view 拖拽滑动效果组件

uniapp自身提供了movable-area/movable-view标签实现拖拽滑动效果,但是实际开发中发现,此标签跟滚动效果有冲突,会影响内部列表的滚动效果,基于此,所以单独封装了两种模式的滑动组件。

定位+TOP

<move-view :min-height="55" @change="moveChange">
	<view id="move_view_tag"></view>
</move-view
<!--
 * @Description: 实现方式:通过absolute+top偏移的方式
 * (效果微有瑕疵,如果top默认是0,实现效果又需要在底部展示的话,页面初始化时会出现闪现的现象,
 * 解决方式:初始top值写大一点,比如屏高或1080)
 * 特别说明:未做兼容处理,可根据具体实现再次优化组件交互等。
-->
<template>
  <view
    class="h-full move-view"
    :style="'top: '+ top + 'px'"
    @touchstart.stop="touchStart"
    @touchend.stop="touchEnd"
    @touchmove.stop="touchMove"
  >
    <slot />
  </view>
</template>
<script>
export default {
  props: {
    minHeight: {
      type: Number,
      default: 20,
    },
  },
  data() {
    return {
      startY: 0,
      top: 1080,
      winHeight: 750,
      initTop: 0,
      moveViewTagHeight: 0,
    };
  },
  mounted() {
    this.winHeight = uni.getSystemInfoSync().windowHeight;
    // 获取初始的偏移量
    const query = uni.createSelectorQuery().in(this);
    query.select('#move_view_tag').boundingClientRect((data) => {
      this.moveViewTagHeight = data.height;
      this.initTop = this.winHeight - data.height;
      this.top = this.initTop;
    }).exec();
  },
  methods: {
    touchStart(e) {
      this.startY = e.touches[0].clientY;
    },
    touchMove(e) {
      const nowY = e.touches[0].clientY;
      this.top = nowY;
      if (this.top <= this.initTop) {
        this.top = this.initTop;
      } else if (this.top >= this.winHeight - this.minHeight) {
        this.top = this.winHeight - this.minHeight;
      }
      this.$emit('change', this.top, nowY - this.startY);
    },
    touchEnd() {
      const dif = this.winHeight - (this.moveViewTagHeight / 2);
      if (this.top >= dif) {
        this.top = this.winHeight - this.minHeight;
      } else {
        this.top = this.initTop;
      }
      this.$emit('touchEnd', this.top);
    },
  },
};
</script>
<style scoped lang="scss">
.move-view {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 9 !important;
}
</style>

定位+translateY

<moveable-view :y="initY" @change="movableChange" @touchEnd="touchEnd">
	<view>你的内容</view>
</moveable-view>
<!--
 * @Description: 实现方式:模拟moveable-area/moveable-view的方式,外层固定盒子,内层通过translateY偏移实现效果。
 * 说明:基本是模拟uniapp movable-area/movable-view 的交互方式,尽量保持组件的纯净性和单一性。
-->
<template>
  <view class="moveable-content">
    <view
      class="moveable-view"
      :style="style"
      @touchstart.stop="touchStart"
      @touchend.stop="touchEnd"
      @touchmove.stop="touchMove"
    >
      <slot />
    </view>
  </view>
</template>
<script>
export default {
  props: {
    y: {
      type: Number,
      default: null,
    },
  },
  data() {
    return {
      style: {
        transform: 'translateX(0px) translateY(0px) translateZ(0px) scale(1)',
      },
      query: null,
      moveBoxHei: 10,
      moveViewHei: 10,
      translateY: 0,
    };
  },
  watch: {
    y: {
      handler(nv) {
        this.translateY = nv;
        this.style = {
          transform: `translateX(0px) translateY(${nv}px) translateZ(0px) scale(1)`,
        };
      },
      deep: true,
      immediate: true,
    },
  },
  mounted() {
	/**
	* 如果是纯vue或H5的项目,应该不需要用以下获取dom的方式,因为框架自动转换的原因,
	* 实际渲染后的像素值跟设定的是有区别的,所以需要动态获取以下高度。
	* (比如:高度100px,浏览器渲染结果可能是98px,真机就可能是120px...)
	*/
    this.query = uni.createSelectorQuery().in(this);
    setTimeout(() => {
      this.query.select('.moveable-content').boundingClientRect((data) => {
        this.moveBoxHei = data.height;
      }).exec();
      this.query.select('.moveable-view').boundingClientRect((data) => {
        this.moveViewHei = data.height;
      }).exec();
    }, 500);
  },
  methods: {
    touchStart(e) {
      this.startY = e.touches[0].clientY;
    },
    touchMove(e) {
      const moveY = Math.floor(e.touches[0].clientY - this.startY);
      const transY = this.translateY + moveY;
      const limit = -(this.moveViewHei - this.moveBoxHei);
      // 偏移限定量,超出向上的最大值后,便不在移动
      if (limit >= transY) {
        this.style = {
          transform: `translateX(0px) translateY(${limit}px) translateZ(0px) scale(1)`,
        };
        // 返回当前偏移量
        this.$emit('change', limit);
        return;
      }

      // eslint-disable-next-line no-nested-ternary
      const y = transY >= 0 ? 0 : transY;
      this.style = {
        transform: `translateX(0px) translateY(${y}px) translateZ(0px) scale(1)`,
      };
      // 返回当前偏移量
      this.$emit('change', y);
    },
    touchEnd() {
      // 返回最大偏移量,父界面有可能会用到(比如超过分界值,直接展开)
      this.$emit('touchEnd', -(this.moveViewHei - this.moveBoxHei));
    },
  },
};
</script>
<style scoped lang="scss">
.moveable-content {
  position: relative;
  width: 100%;
  height: 100%;
  background: #fff;
  min-height: 10px;
  min-width: 10px;
}
.moveable-view {
  width: 100%;
  background: #fff;
  transform-origin: center center;
  will-change: auto;
}
</style>
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值