| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220 |
- import { ref, watch, onUnmounted, unref, type Ref, onMounted } from 'vue';
- import { useScroll, type UseScrollReturn } from '@vueuse/core';
- import gsap from 'gsap';
- const ticker = gsap.ticker;
- export interface AutoScrollOptions {
- /** 延迟(刻),滚动启动、反转、用户交互后的重新开始的延迟 */
- delay?: number;
- /** 滚动到底部后是否回滚(否则是回到顶部重新滚动) */
- rollBack?: boolean;
- /** 每一刻滚动的像素数 */
- step?: number;
- /** 是否自动开始 */
- autoStart?: boolean;
- /** 滚动方向 */
- direction?: 'x' | 'y';
- }
- export interface AutoScrollReturn {
- /** 开始/恢复滚动 */
- start: () => void;
- /** 暂停滚动 */
- pause: () => void;
- /** 重置到初始状态 */
- reset: () => void;
- /** 反转滚动 */
- reverse: () => void;
- /** 恢复滚动 */
- resume: () => void;
- }
- export function useAutoScroll(container: Ref<HTMLElement | null> | HTMLElement | null, options: AutoScrollOptions = {}): AutoScrollReturn {
- const {
- delay = 300, // 默认60帧(约1秒)
- rollBack = false,
- step = 1,
- autoStart = true,
- direction = 'y',
- } = options;
- let cleanupListeners: (() => void) | null = null;
- // 状态管理
- const isActive = ref(false);
- const currentDirection = ref(1); // 1: 正向, -1: 反向
- const delayFrames = ref(0);
- // 使用 VueUse 的 useScroll
- const { arrivedState, x, y } = useScroll(container, {
- behavior: 'smooth',
- }) as UseScrollReturn;
- // 检查是否到达边界
- const checkBoundary = (): boolean => {
- if (direction === 'y') {
- return currentDirection.value > 0 ? arrivedState.bottom : arrivedState.top;
- } else {
- return currentDirection.value > 0 ? arrivedState.right : arrivedState.left;
- }
- };
- // 执行滚动
- const performScroll = () => {
- if (!isActive.value) return;
- if (delayFrames.value > 0) {
- delayFrames.value--;
- return;
- }
- // 检查边界
- if (checkBoundary()) {
- if (rollBack) {
- // 回滚模式:反转方向
- reverse();
- } else {
- // 循环模式:回到开始位置
- reset();
- }
- return;
- }
- // 执行滚动
- if (direction === 'y') {
- y.value += step * currentDirection.value;
- } else {
- x.value += step * currentDirection.value;
- }
- };
- // 开始/恢复滚动
- const start = () => {
- if (!isActive.value) {
- ticker.remove(performScroll);
- ticker.add(performScroll);
- isActive.value = true;
- delayFrames.value = delay;
- }
- };
- // 暂停滚动
- const pause = () => {
- delayFrames.value = Number.MAX_SAFE_INTEGER;
- };
- // 重置到初始状态
- const reset = () => {
- if (direction === 'y') {
- y.value = 0;
- } else {
- x.value = 0;
- }
- currentDirection.value = 1;
- delayFrames.value = delay;
- };
- // 反转滚动方向
- const reverse = () => {
- currentDirection.value *= -1;
- delayFrames.value = delay;
- };
- const resume = () => {
- delayFrames.value = delay;
- };
- // 监听用户交互事件
- const setupUserInteractionListeners = (el: HTMLElement) => {
- // 鼠标滚轮事件
- const handleWheel = () => {
- resume();
- };
- // 鼠标按下事件(用于拖动滚动条)
- const handleMouseDown = () => {
- pause();
- };
- // 鼠标抬起事件
- const handleMouseUp = () => {
- resume();
- };
- // 触摸事件
- const handleTouchStart = () => {
- pause();
- };
- const handleTouchEnd = () => {
- resume();
- };
- // 键盘事件(PageUp/PageDown/方向键)
- const handleKeyDown = (e: KeyboardEvent) => {
- const scrollKeys = ['PageUp', 'PageDown', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'];
- if (scrollKeys.includes(e.key)) {
- pause();
- }
- };
- const handleKeyUp = () => {
- resume();
- };
- // 添加事件监听
- el.addEventListener('wheel', handleWheel, { passive: true });
- el.addEventListener('mousedown', handleMouseDown);
- el.addEventListener('mouseup', handleMouseUp);
- el.addEventListener('touchstart', handleTouchStart, { passive: true });
- el.addEventListener('touchend', handleTouchEnd);
- el.addEventListener('keydown', handleKeyDown);
- el.addEventListener('keyup', handleKeyUp);
- // 返回清理函数
- return () => {
- el.removeEventListener('wheel', handleWheel);
- el.removeEventListener('mousedown', handleMouseDown);
- el.removeEventListener('mouseup', handleMouseUp);
- el.removeEventListener('touchstart', handleTouchStart);
- el.removeEventListener('touchend', handleTouchEnd);
- el.removeEventListener('keydown', handleKeyDown);
- el.removeEventListener('keyup', handleKeyUp);
- };
- };
- // 监听容器变化
- watch(
- () => unref(container),
- (newContainer) => {
- if (cleanupListeners) {
- cleanupListeners();
- cleanupListeners = null;
- }
- if (newContainer) {
- cleanupListeners = setupUserInteractionListeners(newContainer);
- // 容器变化时重置状态
- reset();
- }
- }
- );
- onMounted(() => {
- // 自动开始
- if (autoStart) {
- start();
- }
- });
- // 清理
- onUnmounted(() => {
- ticker.remove(performScroll);
- });
- return {
- start,
- pause,
- reset,
- reverse,
- resume,
- };
- }
|