mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	feat: add SeamlessScroll component
This commit is contained in:
		
							parent
							
								
									db237d2f51
								
							
						
					
					
						commit
						e09ed0fb47
					
				@ -1,15 +1,545 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div></div>
 | 
			
		||||
  <div ref="wrap">
 | 
			
		||||
    <div :style="leftSwitch" v-if="navigation" :class="leftSwitchClass" @click="leftSwitchClick">
 | 
			
		||||
      <slot name="left-switch"></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div :style="rightSwitch" v-if="navigation" :class="rightSwitchClass" @click="rightSwitchClick">
 | 
			
		||||
      <slot name="right-switch"></slot>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div
 | 
			
		||||
      ref="realBox"
 | 
			
		||||
      :style="pos"
 | 
			
		||||
      @mouseenter="enter"
 | 
			
		||||
      @mouseleave="leave"
 | 
			
		||||
      @touchstart="touchStart"
 | 
			
		||||
      @touchmove="touchMove"
 | 
			
		||||
      @touchend="touchEnd"
 | 
			
		||||
    >
 | 
			
		||||
      <div ref="slotList" :style="float">
 | 
			
		||||
        <slot></slot>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div v-html="copyHtml" :style="float"></div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang='ts'>
 | 
			
		||||
export default {
 | 
			
		||||
  name: "SeamlessScroll",
 | 
			
		||||
  setup() {
 | 
			
		||||
    return {};
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
import {
 | 
			
		||||
  defineComponent,
 | 
			
		||||
  computed,
 | 
			
		||||
  ref,
 | 
			
		||||
  unref,
 | 
			
		||||
  watchEffect,
 | 
			
		||||
  nextTick,
 | 
			
		||||
} from "vue";
 | 
			
		||||
import { tryOnMounted, tryOnUnmounted, templateRef } from "@vueuse/core";
 | 
			
		||||
import * as utilsMethods from "./utils";
 | 
			
		||||
const { animationFrame, copyObj } = utilsMethods;
 | 
			
		||||
animationFrame();
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
</style>
 | 
			
		||||
// move动画的animationFrame定时器
 | 
			
		||||
let reqFrame = null;
 | 
			
		||||
let startPos = null;
 | 
			
		||||
// single 单步滚动的定时器
 | 
			
		||||
let singleWaitTime = null;
 | 
			
		||||
//记录touchStart时候的posY
 | 
			
		||||
let startPosY = null;
 | 
			
		||||
//记录touchStart时候的posX
 | 
			
		||||
let startPosX = null;
 | 
			
		||||
// mouseenter mouseleave 控制scrollMove()的开关
 | 
			
		||||
let isHover = false;
 | 
			
		||||
let ease = "ease-in";
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "SeamlessScroll",
 | 
			
		||||
  props: {
 | 
			
		||||
    data: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return [];
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    classOption: {
 | 
			
		||||
      type: Object,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return {};
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  emits: ["ScrollEnd"],
 | 
			
		||||
  setup(props, { emit }) {
 | 
			
		||||
    let xPos = ref(0);
 | 
			
		||||
    let yPos = ref(0);
 | 
			
		||||
    let delay = ref(0);
 | 
			
		||||
    let copyHtml = ref("");
 | 
			
		||||
    let height = ref(0);
 | 
			
		||||
    // 外容器宽度
 | 
			
		||||
    let width = ref(0);
 | 
			
		||||
    // 内容实际宽度
 | 
			
		||||
    let realBoxWidth = ref(0);
 | 
			
		||||
    let realBoxHeight = ref(0);
 | 
			
		||||
 | 
			
		||||
    const wrap = templateRef<HTMLElement | null>("wrap", null);
 | 
			
		||||
    const slotList = templateRef<HTMLElement | null>("slotList", null);
 | 
			
		||||
    const realBox = templateRef<HTMLElement | null>("realBox", null);
 | 
			
		||||
 | 
			
		||||
    let { data, classOption } = props;
 | 
			
		||||
 | 
			
		||||
    let leftSwitchState = computed(() => {
 | 
			
		||||
      return unref(xPos) < 0;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let rightSwitchState = computed(() => {
 | 
			
		||||
      return Math.abs(unref(xPos)) < unref(realBoxWidth) - unref(width);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let defaultOption = computed(() => {
 | 
			
		||||
      return {
 | 
			
		||||
        //步长
 | 
			
		||||
        step: 1,
 | 
			
		||||
        //启动无缝滚动最小数据数
 | 
			
		||||
        limitMoveNum: 5,
 | 
			
		||||
        //是否启用鼠标hover控制
 | 
			
		||||
        hoverStop: true,
 | 
			
		||||
        // bottom 往下 top 往上(默认) left 向左 right 向右
 | 
			
		||||
        direction: "top",
 | 
			
		||||
        //开启移动端touch
 | 
			
		||||
        openTouch: true,
 | 
			
		||||
        //单条数据高度有值hoverStop关闭
 | 
			
		||||
        singleHeight: 0,
 | 
			
		||||
        //单条数据宽度有值hoverStop关闭
 | 
			
		||||
        singleWidth: 0,
 | 
			
		||||
        //单步停止等待时间
 | 
			
		||||
        waitTime: 1000,
 | 
			
		||||
        switchOffset: 30,
 | 
			
		||||
        autoPlay: true,
 | 
			
		||||
        navigation: false,
 | 
			
		||||
        switchSingleStep: 134,
 | 
			
		||||
        switchDelay: 400,
 | 
			
		||||
        switchDisabledClass: "disabled",
 | 
			
		||||
        // singleWidth/singleHeight 是否开启rem度量
 | 
			
		||||
        isSingleRemUnit: false,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let options = computed(() => {
 | 
			
		||||
      // @ts-ignore
 | 
			
		||||
      return copyObj({}, unref(defaultOption), classOption);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let leftSwitchClass = computed(() => {
 | 
			
		||||
      return unref(leftSwitchState) ? "" : unref(options).switchDisabledClass;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let rightSwitchClass = computed(() => {
 | 
			
		||||
      return unref(rightSwitchState) ? "" : unref(options).switchDisabledClass;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let leftSwitch = computed(() => {
 | 
			
		||||
      return {
 | 
			
		||||
        position: "absolute",
 | 
			
		||||
        margin: `${unref(height) / 2}px 0 0 -${unref(options).switchOffset}px`,
 | 
			
		||||
        transform: "translate(-100%,-50%)",
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let rightSwitch = computed(() => {
 | 
			
		||||
      return {
 | 
			
		||||
        position: "absolute",
 | 
			
		||||
        margin: `${unref(height) / 2}px 0 0 ${
 | 
			
		||||
          unref(width) + unref(options).switchOffset
 | 
			
		||||
        }px`,
 | 
			
		||||
        transform: "translateY(-50%)",
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let isHorizontal = computed(() => {
 | 
			
		||||
      return (
 | 
			
		||||
        unref(options).direction !== "bottom" &&
 | 
			
		||||
        unref(options).direction !== "top"
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let float = computed(() => {
 | 
			
		||||
      return unref(isHorizontal)
 | 
			
		||||
        ? { float: "left", overflow: "hidden" }
 | 
			
		||||
        : { overflow: "hidden" };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let pos = computed(() => {
 | 
			
		||||
      return {
 | 
			
		||||
        transform: `translate(${unref(xPos)}px,${unref(yPos)}px)`,
 | 
			
		||||
        transition: `all ${ease} ${unref(delay)}ms`,
 | 
			
		||||
        overflow: "hidden",
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let navigation = computed(() => {
 | 
			
		||||
      return unref(options).navigation;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let autoPlay = computed(() => {
 | 
			
		||||
      if (unref(navigation)) return false;
 | 
			
		||||
      return unref(options).autoPlay;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let scrollSwitch = computed(() => {
 | 
			
		||||
      return data.length >= unref(options).limitMoveNum;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let hoverStopSwitch = computed(() => {
 | 
			
		||||
      return unref(options).hoverStop && unref(autoPlay) && unref(scrollSwitch);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let canTouchScroll = computed(() => {
 | 
			
		||||
      return unref(options).openTouch;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let baseFontSize = computed(() => {
 | 
			
		||||
      return unref(options).isSingleRemUnit
 | 
			
		||||
        ? parseInt(
 | 
			
		||||
            window.getComputedStyle(document.documentElement, null).fontSize
 | 
			
		||||
          )
 | 
			
		||||
        : 1;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let realSingleStopWidth = computed(() => {
 | 
			
		||||
      return unref(options).singleWidth * unref(baseFontSize);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let realSingleStopHeight = computed(() => {
 | 
			
		||||
      return unref(options).singleHeight * unref(baseFontSize);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let step = computed(() => {
 | 
			
		||||
      let singleStep;
 | 
			
		||||
      let step = unref(options).step;
 | 
			
		||||
      if (unref(isHorizontal)) {
 | 
			
		||||
        singleStep = unref(realSingleStopWidth);
 | 
			
		||||
      } else {
 | 
			
		||||
        singleStep = unref(realSingleStopHeight);
 | 
			
		||||
      }
 | 
			
		||||
      if (singleStep > 0 && singleStep % step > 0) {
 | 
			
		||||
        throw "如果设置了单步滚动,step需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确";
 | 
			
		||||
      }
 | 
			
		||||
      return step;
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function reset() {
 | 
			
		||||
      scrollCancle();
 | 
			
		||||
      scrollInitMove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function leftSwitchClick() {
 | 
			
		||||
      if (!unref(leftSwitchState)) return;
 | 
			
		||||
      // 小于单步距离
 | 
			
		||||
      if (Math.abs(unref(xPos)) < unref(options).switchSingleStep) {
 | 
			
		||||
        xPos.value = 0;
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      xPos.value += unref(options).switchSingleStep;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function rightSwitchClick() {
 | 
			
		||||
      if (!unref(rightSwitchState)) return;
 | 
			
		||||
      // 小于单步距离
 | 
			
		||||
      if (
 | 
			
		||||
        unref(realBoxWidth) - unref(width) + unref(xPos) <
 | 
			
		||||
        unref(options).switchSingleStep
 | 
			
		||||
      ) {
 | 
			
		||||
        xPos.value = unref(width) - unref(realBoxWidth);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      xPos.value -= unref(options).switchSingleStep;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function scrollCancle() {
 | 
			
		||||
      cancelAnimationFrame(reqFrame || "");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function touchStart(e) {
 | 
			
		||||
      if (!unref(canTouchScroll)) return;
 | 
			
		||||
      let timer;
 | 
			
		||||
      //touches数组对象获得屏幕上所有的touch,取第一个touch
 | 
			
		||||
      const touch = e.targetTouches[0];
 | 
			
		||||
      const { waitTime, singleHeight, singleWidth } = unref(options);
 | 
			
		||||
      //取第一个touch的坐标值
 | 
			
		||||
      startPos = {
 | 
			
		||||
        x: touch.pageX,
 | 
			
		||||
        y: touch.pageY,
 | 
			
		||||
      };
 | 
			
		||||
      //记录touchStart时候的posY
 | 
			
		||||
      startPosY = unref(yPos);
 | 
			
		||||
      //记录touchStart时候的posX
 | 
			
		||||
      startPosX = unref(xPos);
 | 
			
		||||
      if (!!singleHeight && !!singleWidth) {
 | 
			
		||||
        if (timer) clearTimeout(timer);
 | 
			
		||||
        timer = setTimeout(() => {
 | 
			
		||||
          scrollCancle();
 | 
			
		||||
        }, waitTime + 20);
 | 
			
		||||
      } else {
 | 
			
		||||
        scrollCancle();
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    function touchMove(e) {
 | 
			
		||||
      //当屏幕有多个touch或者页面被缩放过,就不执行move操作
 | 
			
		||||
      if (
 | 
			
		||||
        !unref(canTouchScroll) ||
 | 
			
		||||
        e.targetTouches.length > 1 ||
 | 
			
		||||
        (e.scale && e.scale !== 1)
 | 
			
		||||
      )
 | 
			
		||||
        return;
 | 
			
		||||
      const touch = e.targetTouches[0];
 | 
			
		||||
      const { direction } = unref(options);
 | 
			
		||||
      let endPos = {
 | 
			
		||||
        x: touch.pageX - startPos.x,
 | 
			
		||||
        y: touch.pageY - startPos.y,
 | 
			
		||||
      };
 | 
			
		||||
      //阻止触摸事件的默认行为,即阻止滚屏
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      //dir,1表示纵向滑动,0为横向滑动
 | 
			
		||||
      const dir = Math.abs(endPos.x) < Math.abs(endPos.y) ? 1 : 0;
 | 
			
		||||
      if (
 | 
			
		||||
        (dir === 1 && direction === "bottom") ||
 | 
			
		||||
        (dir === 1 && direction === "top")
 | 
			
		||||
      ) {
 | 
			
		||||
        // 表示纵向滑动 && 运动方向为上下
 | 
			
		||||
        yPos.value = startPosY + endPos.y;
 | 
			
		||||
      } else if (
 | 
			
		||||
        (dir === 0 && direction === "left") ||
 | 
			
		||||
        (dir === 0 && direction === "right")
 | 
			
		||||
      ) {
 | 
			
		||||
        // 为横向滑动 && 运动方向为左右
 | 
			
		||||
        xPos.value = startPosX + endPos.x;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function touchEnd() {
 | 
			
		||||
      if (!unref(canTouchScroll)) return;
 | 
			
		||||
      let timer;
 | 
			
		||||
      const direction = unref(options).direction;
 | 
			
		||||
      delay.value = 50;
 | 
			
		||||
      if (direction === "top") {
 | 
			
		||||
        if (unref(yPos) > 0) yPos.value = 0;
 | 
			
		||||
      } else if (direction === "bottom") {
 | 
			
		||||
        let h = (unref(realBoxHeight) / 2) * -1;
 | 
			
		||||
        if (unref(yPos) < h) yPos.value = h;
 | 
			
		||||
      } else if (direction === "left") {
 | 
			
		||||
        if (unref(xPos) > 0) xPos.value = 0;
 | 
			
		||||
      } else if (direction === "right") {
 | 
			
		||||
        let w = unref(realBoxWidth) * -1;
 | 
			
		||||
        if (unref(xPos) < w) xPos.value = w;
 | 
			
		||||
      }
 | 
			
		||||
      if (timer) clearTimeout(timer);
 | 
			
		||||
      timer = setTimeout(() => {
 | 
			
		||||
        delay.value = 0;
 | 
			
		||||
        scrollMove();
 | 
			
		||||
      }, unref(delay));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function enter() {
 | 
			
		||||
      if (unref(hoverStopSwitch)) scrollStopMove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function leave() {
 | 
			
		||||
      if (unref(hoverStopSwitch)) scrollStartMove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function scrollMove() {
 | 
			
		||||
      // 鼠标移入时拦截scrollMove()
 | 
			
		||||
      if (isHover) return;
 | 
			
		||||
      //进入move立即先清除动画 防止频繁touchMove导致多动画同时进行
 | 
			
		||||
      scrollCancle();
 | 
			
		||||
      reqFrame = requestAnimationFrame(function () {
 | 
			
		||||
        //实际高度
 | 
			
		||||
        const h = unref(realBoxHeight) / 2;
 | 
			
		||||
        //宽度
 | 
			
		||||
        const w = unref(realBoxWidth) / 2;
 | 
			
		||||
        let { direction, waitTime } = unref(options);
 | 
			
		||||
        if (direction === "top") {
 | 
			
		||||
          // 上
 | 
			
		||||
          if (Math.abs(unref(yPos)) >= h) {
 | 
			
		||||
            emit("ScrollEnd");
 | 
			
		||||
            yPos.value = 0;
 | 
			
		||||
          }
 | 
			
		||||
          yPos.value -= step.value;
 | 
			
		||||
        } else if (direction === "bottom") {
 | 
			
		||||
          // 下
 | 
			
		||||
          if (unref(yPos) >= 0) {
 | 
			
		||||
            emit("ScrollEnd");
 | 
			
		||||
            yPos.value = h * -1;
 | 
			
		||||
          }
 | 
			
		||||
          yPos.value += step.value;
 | 
			
		||||
        } else if (direction === "left") {
 | 
			
		||||
          // 左
 | 
			
		||||
          if (Math.abs(unref(xPos)) >= w) {
 | 
			
		||||
            emit("ScrollEnd");
 | 
			
		||||
            xPos.value = 0;
 | 
			
		||||
          }
 | 
			
		||||
          xPos.value -= step.value;
 | 
			
		||||
        } else if (direction === "right") {
 | 
			
		||||
          // 右
 | 
			
		||||
          if (unref(xPos) >= 0) {
 | 
			
		||||
            emit("ScrollEnd");
 | 
			
		||||
            xPos.value = w * -1;
 | 
			
		||||
          }
 | 
			
		||||
          xPos.value += step.value;
 | 
			
		||||
        }
 | 
			
		||||
        if (singleWaitTime) clearTimeout(singleWaitTime);
 | 
			
		||||
        if (!!unref(realSingleStopHeight)) {
 | 
			
		||||
          //是否启动了单行暂停配置
 | 
			
		||||
          if (
 | 
			
		||||
            Math.abs(unref(yPos)) % unref(realSingleStopHeight) <
 | 
			
		||||
            unref(step)
 | 
			
		||||
          ) {
 | 
			
		||||
            // 符合条件暂停waitTime
 | 
			
		||||
            singleWaitTime = setTimeout(() => {
 | 
			
		||||
              scrollMove();
 | 
			
		||||
            }, waitTime);
 | 
			
		||||
          } else {
 | 
			
		||||
            scrollMove();
 | 
			
		||||
          }
 | 
			
		||||
        } else if (!!unref(realSingleStopWidth)) {
 | 
			
		||||
          if (
 | 
			
		||||
            Math.abs(unref(xPos)) % unref(realSingleStopWidth) <
 | 
			
		||||
            unref(step)
 | 
			
		||||
          ) {
 | 
			
		||||
            // 符合条件暂停waitTime
 | 
			
		||||
            singleWaitTime = setTimeout(() => {
 | 
			
		||||
              scrollMove();
 | 
			
		||||
            }, waitTime);
 | 
			
		||||
          } else {
 | 
			
		||||
            scrollMove();
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          scrollMove();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function scrollInitMove() {
 | 
			
		||||
      nextTick(() => {
 | 
			
		||||
        const { switchDelay } = unref(options);
 | 
			
		||||
        //清空copy
 | 
			
		||||
        copyHtml.value = "";
 | 
			
		||||
        if (unref(isHorizontal)) {
 | 
			
		||||
          height.value = unref(wrap).offsetHeight;
 | 
			
		||||
          width.value = unref(wrap).offsetWidth;
 | 
			
		||||
          let slotListWidth = unref(slotList).offsetWidth;
 | 
			
		||||
          // 水平滚动设置warp width
 | 
			
		||||
          if (unref(autoPlay)) {
 | 
			
		||||
            // 修正offsetWidth四舍五入
 | 
			
		||||
            slotListWidth = slotListWidth * 2 + 1;
 | 
			
		||||
          }
 | 
			
		||||
          unref(realBox).style.width = slotListWidth + "px";
 | 
			
		||||
          realBoxWidth.value = slotListWidth;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (unref(autoPlay)) {
 | 
			
		||||
          ease = "ease-in";
 | 
			
		||||
          delay.value = 0;
 | 
			
		||||
        } else {
 | 
			
		||||
          ease = "linear";
 | 
			
		||||
          delay.value = switchDelay;
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 是否可以滚动判断
 | 
			
		||||
        if (unref(scrollSwitch)) {
 | 
			
		||||
          let timer;
 | 
			
		||||
          if (timer) clearTimeout(timer);
 | 
			
		||||
          copyHtml.value = unref(slotList).innerHTML;
 | 
			
		||||
          setTimeout(() => {
 | 
			
		||||
            realBoxHeight.value = unref(realBox).offsetHeight;
 | 
			
		||||
            scrollMove();
 | 
			
		||||
          }, 0);
 | 
			
		||||
        } else {
 | 
			
		||||
          scrollCancle();
 | 
			
		||||
          yPos.value = xPos.value = 0;
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function scrollStartMove() {
 | 
			
		||||
      //开启scrollMove
 | 
			
		||||
      isHover = false;
 | 
			
		||||
      scrollMove();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function scrollStopMove() {
 | 
			
		||||
      //关闭scrollMove
 | 
			
		||||
      isHover = true;
 | 
			
		||||
      // 防止频频hover进出单步滚动,导致定时器乱掉
 | 
			
		||||
      if (singleWaitTime) clearTimeout(singleWaitTime);
 | 
			
		||||
      scrollCancle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    watchEffect(() => {
 | 
			
		||||
      const watchData = data;
 | 
			
		||||
      if (!watchData) return;
 | 
			
		||||
      nextTick(() => {
 | 
			
		||||
        reset();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      const watchAutoPlay = unref(autoPlay);
 | 
			
		||||
      if (watchAutoPlay) {
 | 
			
		||||
        reset();
 | 
			
		||||
      } else {
 | 
			
		||||
        scrollStopMove();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    tryOnMounted(() => {
 | 
			
		||||
      scrollInitMove();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    tryOnUnmounted(() => {
 | 
			
		||||
      scrollCancle();
 | 
			
		||||
      clearTimeout(singleWaitTime);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      xPos,
 | 
			
		||||
      yPos,
 | 
			
		||||
      delay,
 | 
			
		||||
      copyHtml,
 | 
			
		||||
      height,
 | 
			
		||||
      width,
 | 
			
		||||
      realBoxWidth,
 | 
			
		||||
      leftSwitchState,
 | 
			
		||||
      rightSwitchState,
 | 
			
		||||
      defaultOption,
 | 
			
		||||
      options,
 | 
			
		||||
      leftSwitchClass,
 | 
			
		||||
      rightSwitchClass,
 | 
			
		||||
      leftSwitch,
 | 
			
		||||
      rightSwitch,
 | 
			
		||||
      isHorizontal,
 | 
			
		||||
      float,
 | 
			
		||||
      pos,
 | 
			
		||||
      navigation,
 | 
			
		||||
      autoPlay,
 | 
			
		||||
      scrollSwitch,
 | 
			
		||||
      hoverStopSwitch,
 | 
			
		||||
      canTouchScroll,
 | 
			
		||||
      baseFontSize,
 | 
			
		||||
      realSingleStopWidth,
 | 
			
		||||
      realSingleStopHeight,
 | 
			
		||||
      step,
 | 
			
		||||
      reset,
 | 
			
		||||
      leftSwitchClick,
 | 
			
		||||
      rightSwitchClick,
 | 
			
		||||
      scrollCancle,
 | 
			
		||||
      touchStart,
 | 
			
		||||
      touchMove,
 | 
			
		||||
      touchEnd,
 | 
			
		||||
      enter,
 | 
			
		||||
      leave,
 | 
			
		||||
      scrollMove,
 | 
			
		||||
      scrollInitMove,
 | 
			
		||||
      scrollStartMove,
 | 
			
		||||
      scrollStopMove,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @desc AnimationFrame简单兼容hack
 | 
			
		||||
 */
 | 
			
		||||
const animationFrame = () => {
 | 
			
		||||
export const animationFrame = () => {
 | 
			
		||||
  window.cancelAnimationFrame = (function () {
 | 
			
		||||
    return window.cancelAnimationFrame ||
 | 
			
		||||
      window.webkitCancelAnimationFrame ||
 | 
			
		||||
@ -31,7 +31,7 @@ const animationFrame = () => {
 | 
			
		||||
 * @param {arr1,arr2}
 | 
			
		||||
 * @return {Boolean}
 | 
			
		||||
 */
 | 
			
		||||
const arrayEqual = (arr1: Array<any>, arr2: Array<any>) => {
 | 
			
		||||
export const arrayEqual = (arr1: Array<any>, arr2: Array<any>) => {
 | 
			
		||||
  if (arr1 === arr2) return true
 | 
			
		||||
  if (arr1.length !== arr2.length) return false
 | 
			
		||||
  for (let i = 0; i < arr1.length; ++i) {
 | 
			
		||||
@ -43,7 +43,7 @@ const arrayEqual = (arr1: Array<any>, arr2: Array<any>) => {
 | 
			
		||||
/**
 | 
			
		||||
 * @desc 深浅合并拷贝
 | 
			
		||||
 */
 | 
			
		||||
function copyObj() {
 | 
			
		||||
export function copyObj() {
 | 
			
		||||
  if (!Array.isArray) {
 | 
			
		||||
    // @ts-expect-error
 | 
			
		||||
    Array.isArray = function (arg) {
 | 
			
		||||
@ -104,11 +104,5 @@ function copyObj() {
 | 
			
		||||
  return target
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  animationFrame,
 | 
			
		||||
  arrayEqual,
 | 
			
		||||
  copyObj
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -18,5 +18,6 @@
 | 
			
		||||
  "cropping": "图片裁剪",
 | 
			
		||||
  "countTo": "数字动画",
 | 
			
		||||
  "selector": "选择器组件",
 | 
			
		||||
  "flowChart": "流程图"
 | 
			
		||||
  "flowChart": "流程图",
 | 
			
		||||
  "seamless": "无缝滚动"
 | 
			
		||||
}
 | 
			
		||||
@ -18,5 +18,6 @@
 | 
			
		||||
  "cropping": "Picture Cropping",
 | 
			
		||||
  "countTo": "Digital Animation",
 | 
			
		||||
  "selector": "Selector Components",
 | 
			
		||||
  "flowChart": "flow Chart"
 | 
			
		||||
  "flowChart": "flow Chart",
 | 
			
		||||
  "seamless": "Seamless Scroll"
 | 
			
		||||
}
 | 
			
		||||
@ -104,6 +104,15 @@ const routes: Array<RouteRecordRaw> = [
 | 
			
		||||
          savedPosition: true
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: '/components/seamlessScroll',
 | 
			
		||||
        component: () => import(/* webpackChunkName: "components" */ '../views/components/seamless-scroll/index.vue'),
 | 
			
		||||
        meta: {
 | 
			
		||||
          title: 'seamless',
 | 
			
		||||
          showLink: false,
 | 
			
		||||
          savedPosition: true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      // {
 | 
			
		||||
      //   path: '/components/flowChart',
 | 
			
		||||
      //   component: () => import(/* webpackChunkName: "components" */ '../views/components/flow-chart/index.vue'),
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div style="margin: 10px">
 | 
			
		||||
    <div class="cropper-container">
 | 
			
		||||
      <Cropper ref="refCropper" :width="'45vw'" :src="img" />
 | 
			
		||||
      <Cropper ref="refCropper" :width="'40vw'" :src="img" />
 | 
			
		||||
      <img :src="cropperImg" class="croppered" v-if="cropperImg" />
 | 
			
		||||
    </div>
 | 
			
		||||
    <el-button type="primary" @click="onCropper">裁剪</el-button>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										126
									
								
								src/views/components/seamless-scroll/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/views/components/seamless-scroll/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,126 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-space wrap>
 | 
			
		||||
    <el-card class="box-card">
 | 
			
		||||
      <template #header>
 | 
			
		||||
        <div class="card-header">
 | 
			
		||||
          <span>无缝滚动示例</span>
 | 
			
		||||
          <el-button class="button" type="text" @click="changeDirection('top')">向上滚动</el-button>
 | 
			
		||||
          <el-button class="button" type="text" @click="changeDirection('bottom')">向下滚动</el-button>
 | 
			
		||||
          <el-button class="button" type="text" @click="changeDirection('left')">向左滚动</el-button>
 | 
			
		||||
          <el-button class="button" type="text" @click="changeDirection('right')">向右滚动</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
      <SeamlessScroll ref="scroll" :data="listData" :class-option="classOption" class="warp">
 | 
			
		||||
        <ul class="item">
 | 
			
		||||
          <li v-for="(item, index) in listData" :key="index">
 | 
			
		||||
            <span class="title" v-text="item.title"></span>
 | 
			
		||||
            <span class="date" v-text="item.date"></span>
 | 
			
		||||
          </li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </SeamlessScroll>
 | 
			
		||||
    </el-card>
 | 
			
		||||
  </el-space>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang='ts'>
 | 
			
		||||
import { ref, unref } from "vue";
 | 
			
		||||
import { templateRef } from "@vueuse/core";
 | 
			
		||||
 | 
			
		||||
import SeamlessScroll from "/@/components/SeamlessScroll";
 | 
			
		||||
export default {
 | 
			
		||||
  components: {
 | 
			
		||||
    SeamlessScroll,
 | 
			
		||||
  },
 | 
			
		||||
  setup() {
 | 
			
		||||
    const scroll = templateRef<HTMLElement | null>("scroll", null);
 | 
			
		||||
 | 
			
		||||
    let listData = ref([
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第一行无缝滚动第一行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第二行无缝滚动第二行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第三行无缝滚动第三行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第四行无缝滚动第四行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第五行无缝滚动第五行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第六行无缝滚动第六行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第七行无缝滚动第七行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第八行无缝滚动第八行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        title: "无缝滚动第九行无缝滚动第九行",
 | 
			
		||||
        date: "2021-5-1",
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    let classOption = ref({
 | 
			
		||||
      direction: "top",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function changeDirection(val) {
 | 
			
		||||
      scroll.value.scrollInitMove();
 | 
			
		||||
      classOption.value.direction = val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      listData,
 | 
			
		||||
      classOption,
 | 
			
		||||
      changeDirection,
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.box-card {
 | 
			
		||||
  margin: 10px;
 | 
			
		||||
}
 | 
			
		||||
.card-header {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  span {
 | 
			
		||||
    margin-right: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
.warp {
 | 
			
		||||
  height: 270px;
 | 
			
		||||
  width: 360px;
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  ul {
 | 
			
		||||
    list-style: none;
 | 
			
		||||
    padding: 0;
 | 
			
		||||
    margin: 0 auto;
 | 
			
		||||
    li,
 | 
			
		||||
    a {
 | 
			
		||||
      display: block;
 | 
			
		||||
      height: 30px;
 | 
			
		||||
      line-height: 30px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      justify-content: space-between;
 | 
			
		||||
      font-size: 15px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user