wangrong
7 天以前 5ebdf612b8291106370bba5e79996b28a7452e67
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<template>
    <div class="auto-scroll" @mouseenter="pause" @mouseleave="resume">
        <div v-if="$slots.head" class="auto-scroll__head">
            <slot name="head" />
        </div>
        <div ref="viewportRef" class="auto-scroll__viewport">
            <div ref="contentRef" class="auto-scroll__content">
                <slot />
            </div>
        </div>
    </div>
</template>
 
<script lang="ts" setup>
defineOptions({
    name: 'report-screen-auto-scroll'
});
 
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
 
const props = withDefaults(
    defineProps<{
        /** 滚动步进像素 */
        step?: number;
        /** 滚动间隔 ms */
        interval?: number;
        /** 依赖变化时重新检测是否需要滚动(如列表数据) */
        watchKey?: string | number;
    }>(),
    {
        step: 1,
        interval: 40
    }
);
 
const viewportRef = ref<HTMLElement | null>(null);
const contentRef = ref<HTMLElement | null>(null);
const paused = ref(false);
const direction = ref(1);
let timer: ReturnType<typeof setInterval> | null = null;
let resizeObserver: ResizeObserver | null = null;
 
function canScroll() {
    const el = viewportRef.value;
    if (!el) return false;
    return el.scrollHeight > el.clientHeight + 2;
}
 
function tick() {
    const el = viewportRef.value;
    if (!el || paused.value || !canScroll()) return;
 
    const max = el.scrollHeight - el.clientHeight;
    let next = el.scrollTop + props.step * direction.value;
 
    if (next >= max) {
        next = max;
        direction.value = -1;
    } else if (next <= 0) {
        next = 0;
        direction.value = 1;
    }
 
    el.scrollTop = next;
}
 
function start() {
    stop();
    timer = setInterval(tick, props.interval);
}
 
function stop() {
    if (timer) {
        clearInterval(timer);
        timer = null;
    }
}
 
function pause() {
    paused.value = true;
}
 
function resume() {
    paused.value = false;
}
 
async function reset() {
    await nextTick();
    const el = viewportRef.value;
    if (el) el.scrollTop = 0;
    direction.value = 1;
}
 
onMounted(() => {
    start();
    const el = viewportRef.value;
    if (el && typeof ResizeObserver !== 'undefined') {
        resizeObserver = new ResizeObserver(() => {
            reset();
        });
        resizeObserver.observe(el);
        if (contentRef.value) resizeObserver.observe(contentRef.value);
    }
    window.addEventListener('resize', reset);
    window.addEventListener('report-screen-fullscreen-change', reset);
});
 
onUnmounted(() => {
    stop();
    resizeObserver?.disconnect();
    window.removeEventListener('resize', reset);
    window.removeEventListener('report-screen-fullscreen-change', reset);
});
 
watch(
    () => props.watchKey,
    () => reset()
);
</script>
 
<style lang="scss" scoped>
.auto-scroll {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
 
    &__head {
        flex-shrink: 0;
    }
 
    &__viewport {
        flex: 1;
        min-height: 0;
        overflow: hidden;
    }
 
    &__content {
        min-height: min-content;
    }
}
</style>