|
|
@@ -24,27 +24,36 @@ export default {
|
|
|
},
|
|
|
computed: {
|
|
|
scrollData() {
|
|
|
+ // 当数据量大于 limit 时,开启滚动
|
|
|
if (this.data && this.data.length > this.limit) {
|
|
|
this.isScrollable = true;
|
|
|
+
|
|
|
const original = this.data.map((item, index) => ({
|
|
|
...item,
|
|
|
_originalIndex: index
|
|
|
}));
|
|
|
- const clone = JSON.parse(JSON.stringify(this.data)).map((item, index) => ({
|
|
|
- ...item,
|
|
|
- _clone_id: `clone_${Date.now()}_${index}`,
|
|
|
- _originalIndex: index
|
|
|
- }));
|
|
|
- return [...original, ...clone];
|
|
|
+
|
|
|
+ // 【大屏终极修复】强制克隆多份(总共 4 份),保证内容高度绝对碾压任何全屏容器
|
|
|
+ let result = [...original];
|
|
|
+ for (let i = 0; i < 3; i++) {
|
|
|
+ const clone = JSON.parse(JSON.stringify(this.data)).map((item, index) => ({
|
|
|
+ ...item,
|
|
|
+ _clone_id: `clone_${Date.now()}_${i}_${index}`,
|
|
|
+ _originalIndex: index
|
|
|
+ }));
|
|
|
+ result = result.concat(clone);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
}
|
|
|
+
|
|
|
this.isScrollable = false;
|
|
|
return this.data.map((item, index) => ({ ...item, _originalIndex: index }));
|
|
|
}
|
|
|
},
|
|
|
- // 加入 mounted 钩子,确保 DOM 绝对渲染完毕再测算
|
|
|
mounted() {
|
|
|
console.log('✅ SeamlessScroll: 组件已挂载,准备初始化滚动');
|
|
|
this.initScroll();
|
|
|
+
|
|
|
// 全屏切换后容器高度变化,需要重新初始化滚动
|
|
|
this._onFullscreenChange = () => {
|
|
|
setTimeout(() => this.initScroll(), 300);
|
|
|
@@ -53,11 +62,12 @@ export default {
|
|
|
},
|
|
|
watch: {
|
|
|
data: {
|
|
|
- handler() {
|
|
|
+ handler() {
|
|
|
console.log('🔄 SeamlessScroll: 监测到数据变化');
|
|
|
- this.initScroll();
|
|
|
+ this.initScroll();
|
|
|
},
|
|
|
- deep: true
|
|
|
+ deep: true,
|
|
|
+ immediate: true
|
|
|
}
|
|
|
},
|
|
|
beforeDestroy() {
|
|
|
@@ -73,17 +83,19 @@ export default {
|
|
|
methods: {
|
|
|
initScroll() {
|
|
|
this.pause();
|
|
|
+
|
|
|
// 清除上一次未执行完的延时器,防止多个 setTimeout 同时触发 resume
|
|
|
if (this._initTimer) {
|
|
|
clearTimeout(this._initTimer);
|
|
|
this._initTimer = null;
|
|
|
}
|
|
|
+
|
|
|
this.currentTop = 0;
|
|
|
if (this.$refs.scrollRef) this.$refs.scrollRef.scrollTop = 0;
|
|
|
|
|
|
// 直接判断数据量,不依赖 computed 副作用的时序
|
|
|
if (!this.data || this.data.length <= this.limit) {
|
|
|
- console.log('🛑 SeamlessScroll: 数据量不足,无需滚动');
|
|
|
+ console.log('🛑 SeamlessScroll: 数据量不足,无需滚动', this.data, this.limit);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
@@ -94,15 +106,16 @@ export default {
|
|
|
const wrapper = this.$refs.scrollRef;
|
|
|
if (!wrapper) return;
|
|
|
|
|
|
- const measureEl = wrapper.querySelector(this.measureSelector);
|
|
|
-
|
|
|
- // 如果容器高度等于或大于内容高度,说明没有溢出,肯定滚不动
|
|
|
+ // 如果 4 份数据加起来都没容器高,说明数据极其短,强制取消滚动避免报错
|
|
|
if (wrapper.scrollHeight <= wrapper.clientHeight) {
|
|
|
- console.warn('⚠️ SeamlessScroll 警告: 内容高度没有超出容器高度,滚动被迫终止!请检查外部 CSS 高度限制。');
|
|
|
+ console.warn('⚠️ SeamlessScroll: 数据总高度依然小于容器,取消滚动。');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- this.resetHeight = measureEl ? measureEl.offsetHeight / 2 : wrapper.scrollHeight / 2;
|
|
|
+ const measureEl = wrapper.querySelector(this.measureSelector);
|
|
|
+
|
|
|
+ // 【核心修复】因为 computed 里总共渲染了 4 份数据,所以总高度要除以 4 才能得到单份的真实复位高度
|
|
|
+ this.resetHeight = measureEl ? measureEl.offsetHeight / 4 : wrapper.scrollHeight / 4;
|
|
|
|
|
|
this.resume();
|
|
|
}, 100);
|
|
|
@@ -110,8 +123,10 @@ export default {
|
|
|
},
|
|
|
resume() {
|
|
|
if ((!this.data || this.data.length <= this.limit) || this.resetHeight <= 0) return;
|
|
|
+
|
|
|
// 防止重复调用产生多个动画循环
|
|
|
this.pause();
|
|
|
+
|
|
|
const step = () => {
|
|
|
const wrapper = this.$refs.scrollRef;
|
|
|
if (!wrapper) {
|
|
|
@@ -125,12 +140,16 @@ export default {
|
|
|
|
|
|
// 到达复位点,或者已滚到底部无法继续时,都重置
|
|
|
const maxScroll = wrapper.scrollHeight - wrapper.clientHeight;
|
|
|
+
|
|
|
+ // 只要卷去的高度达到了单份数据的真实高度,就瞬间复位,实现无缝循环
|
|
|
if (wrapper.scrollTop >= this.resetHeight || (maxScroll > 0 && wrapper.scrollTop >= maxScroll)) {
|
|
|
this.currentTop = 0;
|
|
|
wrapper.scrollTop = 0;
|
|
|
}
|
|
|
+
|
|
|
this.scrollTimer = requestAnimationFrame(step);
|
|
|
};
|
|
|
+
|
|
|
this.scrollTimer = requestAnimationFrame(step);
|
|
|
},
|
|
|
pause() {
|