|
@@ -1,3 +1,495 @@
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, ref, unref, nextTick } from "vue";
|
|
|
+import type { CSSProperties, PropType } from "vue";
|
|
|
+import {
|
|
|
+ tryOnMounted,
|
|
|
+ tryOnUnmounted,
|
|
|
+ templateRef,
|
|
|
+ useDebounceFn
|
|
|
+} from "@vueuse/core";
|
|
|
+import * as utilsMethods from "./utils";
|
|
|
+const { animationFrame, copyObj } = utilsMethods;
|
|
|
+animationFrame();
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ data: {
|
|
|
+ type: Array as PropType<unknown>
|
|
|
+ },
|
|
|
+ classOption: {
|
|
|
+ type: Object as PropType<unknown>
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+const emit = defineEmits<{
|
|
|
+ (e: "scrollEnd"): void;
|
|
|
+}>();
|
|
|
+
|
|
|
+let xPos = ref<number>(0);
|
|
|
+let yPos = ref<number>(0);
|
|
|
+let delay = ref<number>(0);
|
|
|
+let height = ref<number>(0);
|
|
|
+// 外容器宽度
|
|
|
+let width = ref<number>(0);
|
|
|
+// 内容实际宽度
|
|
|
+let realBoxWidth = ref<number>(0);
|
|
|
+let realBoxHeight = ref<number>(0);
|
|
|
+let copyHtml = ref("");
|
|
|
+// single 单步滚动的定时器
|
|
|
+let singleWaitTime = null;
|
|
|
+// move动画的animationFrame定时器
|
|
|
+let reqFrame = null;
|
|
|
+let startPos = null;
|
|
|
+//记录touchStart时候的posY
|
|
|
+let startPosY = null;
|
|
|
+//记录touchStart时候的posX
|
|
|
+let startPosX = null;
|
|
|
+// mouseenter mouseleave 控制scrollMove()的开关
|
|
|
+let isHover = false;
|
|
|
+let ease = "ease-in";
|
|
|
+
|
|
|
+// eslint-disable-next-line vue/no-setup-props-destructure
|
|
|
+let { classOption } = props;
|
|
|
+
|
|
|
+if (classOption["key"] === undefined) {
|
|
|
+ classOption["key"] = 0;
|
|
|
+}
|
|
|
+
|
|
|
+const wrap = templateRef<HTMLElement | null>(`wrap${classOption["key"]}`, null);
|
|
|
+const slotList = templateRef<HTMLElement | null>(
|
|
|
+ `slotList${classOption["key"]}`,
|
|
|
+ null
|
|
|
+);
|
|
|
+const realBox = templateRef<HTMLElement | null>(
|
|
|
+ `realBox${classOption["key"]}`,
|
|
|
+ null
|
|
|
+);
|
|
|
+
|
|
|
+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);
|
|
|
+});
|
|
|
+
|
|
|
+const leftSwitchClass = computed(() => {
|
|
|
+ return unref(leftSwitchState) ? "" : unref(options).switchDisabledClass;
|
|
|
+});
|
|
|
+
|
|
|
+let rightSwitchClass = computed(() => {
|
|
|
+ return unref(rightSwitchState) ? "" : unref(options).switchDisabledClass;
|
|
|
+});
|
|
|
+
|
|
|
+let leftSwitch = computed((): CSSProperties => {
|
|
|
+ return {
|
|
|
+ position: "absolute",
|
|
|
+ margin: `${unref(height) / 2}px 0 0 -${unref(options).switchOffset}px`,
|
|
|
+ transform: "translate(-100%,-50%)"
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+let rightSwitch = computed((): CSSProperties => {
|
|
|
+ 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((): CSSProperties => {
|
|
|
+ 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(() => {
|
|
|
+ // 从 props 解构出来的 属性 不再具有相应性.
|
|
|
+ return props.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() {
|
|
|
+ xPos.value = 0;
|
|
|
+ yPos.value = 0;
|
|
|
+ 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();
|
|
|
+}
|
|
|
+
|
|
|
+// 鼠标滚轮事件
|
|
|
+function wheel(e) {
|
|
|
+ e.preventDefault();
|
|
|
+ if (
|
|
|
+ unref(options).direction === "left" ||
|
|
|
+ unref(options).direction === "right"
|
|
|
+ )
|
|
|
+ return;
|
|
|
+ useDebounceFn(() => {
|
|
|
+ e.deltaY > 0 ? (yPos.value -= step.value) : (yPos.value += step.value);
|
|
|
+ }, 50)();
|
|
|
+}
|
|
|
+
|
|
|
+// watchEffect(() => {
|
|
|
+// const watchData = data;
|
|
|
+// if (!watchData) return;
|
|
|
+// nextTick(() => {
|
|
|
+// reset();
|
|
|
+// });
|
|
|
+
|
|
|
+// const watchAutoPlay = unref(autoPlay);
|
|
|
+// if (watchAutoPlay) {
|
|
|
+// reset();
|
|
|
+// } else {
|
|
|
+// scrollStopMove();
|
|
|
+// }
|
|
|
+// });
|
|
|
+
|
|
|
+tryOnMounted(() => {
|
|
|
+ scrollInitMove();
|
|
|
+});
|
|
|
+
|
|
|
+tryOnUnmounted(() => {
|
|
|
+ scrollCancle();
|
|
|
+ clearTimeout(singleWaitTime);
|
|
|
+});
|
|
|
+
|
|
|
+defineExpose({
|
|
|
+ reset
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
<template>
|
|
|
<div :ref="'wrap' + classOption['key']">
|
|
|
<div
|
|
@@ -33,553 +525,3 @@
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
-
|
|
|
-<script lang="ts">
|
|
|
-import { defineComponent, computed, ref, unref, nextTick } from "vue";
|
|
|
-import {
|
|
|
- tryOnMounted,
|
|
|
- tryOnUnmounted,
|
|
|
- templateRef,
|
|
|
- useDebounceFn
|
|
|
-} from "@vueuse/core";
|
|
|
-import * as utilsMethods from "./utils";
|
|
|
-const { animationFrame, copyObj } = utilsMethods;
|
|
|
-animationFrame();
|
|
|
-
|
|
|
-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);
|
|
|
-
|
|
|
- // single 单步滚动的定时器
|
|
|
- let singleWaitTime = null;
|
|
|
- // move动画的animationFrame定时器
|
|
|
- let reqFrame = null;
|
|
|
- let startPos = null;
|
|
|
- //记录touchStart时候的posY
|
|
|
- let startPosY = null;
|
|
|
- //记录touchStart时候的posX
|
|
|
- let startPosX = null;
|
|
|
- // mouseenter mouseleave 控制scrollMove()的开关
|
|
|
- let isHover = false;
|
|
|
- let ease = "ease-in";
|
|
|
-
|
|
|
- // eslint-disable-next-line vue/no-setup-props-destructure
|
|
|
- let { classOption } = props;
|
|
|
-
|
|
|
- if (classOption["key"] === undefined) {
|
|
|
- classOption["key"] = 0;
|
|
|
- }
|
|
|
-
|
|
|
- const wrap = templateRef<HTMLElement | null>(
|
|
|
- `wrap${classOption["key"]}`,
|
|
|
- null
|
|
|
- );
|
|
|
- const slotList = templateRef<HTMLElement | null>(
|
|
|
- `slotList${classOption["key"]}`,
|
|
|
- null
|
|
|
- );
|
|
|
- const realBox = templateRef<HTMLElement | null>(
|
|
|
- `realBox${classOption["key"]}`,
|
|
|
- null
|
|
|
- );
|
|
|
-
|
|
|
- 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(() => {
|
|
|
- // 从 props 解构出来的 属性 不再具有相应性.
|
|
|
- return props.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() {
|
|
|
- xPos.value = 0;
|
|
|
- yPos.value = 0;
|
|
|
- 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();
|
|
|
- // }
|
|
|
- // });
|
|
|
-
|
|
|
- // 鼠标滚轮事件
|
|
|
- function wheel(e) {
|
|
|
- e.preventDefault();
|
|
|
- if (
|
|
|
- unref(options).direction === "left" ||
|
|
|
- unref(options).direction === "right"
|
|
|
- )
|
|
|
- return;
|
|
|
- useDebounceFn(() => {
|
|
|
- e.deltaY > 0 ? (yPos.value -= step.value) : (yPos.value += step.value);
|
|
|
- }, 50)();
|
|
|
- }
|
|
|
-
|
|
|
- tryOnMounted(() => {
|
|
|
- scrollInitMove();
|
|
|
- });
|
|
|
-
|
|
|
- tryOnUnmounted(() => {
|
|
|
- scrollCancle();
|
|
|
- clearTimeout(singleWaitTime);
|
|
|
- });
|
|
|
-
|
|
|
- return {
|
|
|
- xPos,
|
|
|
- yPos,
|
|
|
- delay,
|
|
|
- copyHtml,
|
|
|
- height,
|
|
|
- width,
|
|
|
- realBoxWidth,
|
|
|
- leftSwitchState,
|
|
|
- rightSwitchState,
|
|
|
- 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,
|
|
|
- wheel
|
|
|
- };
|
|
|
- }
|
|
|
-});
|
|
|
-</script>
|