我有一个事件日志。各种组件向其中添加事件。如果达到最大值,则将删除最早的事件。添加新事件并将用户滚动到底部时,我使用vue-chat-scroll将其保持在底部。该列表带有过渡组的动画。
一切正常,直到旧邮件开始被删除为止。用户滚动位置应随项目一起移动,以使它们看起来静止。因为它是动画,所以我不知道如何正确地操作滚动。
<template>
<b-container>
<b-row class="invisible">Place holder</b-row>
<b-row
cols="1"
class="scroll-container align-content-start mt-4"
v-chat-scroll="{ always: false, smooth: true }"
>
<b-col class="p-0">
<transition-group name="list" tag="ol" class="d-block w-100 p-0">
<li
v-for="event in eventLog"
:key="event.id"
class="list-unstyled w-100"
>
<b-container>
<b-row>
<b-col
cols="auto"
class="rounded p-1 mr-1 bg-secondary text-white mt-1"
>
16:34:35
</b-col>
<b-col
class="rounded p-1 mt-1"
:class="`bg-${event.variant ? event.variant : 'light'}`"
>
{{ event.title }}
</b-col>
</b-row>
</b-container>
</li>
</transition-group>
</b-col>
</b-row>
<b-row class="invisible">Place holder</b-row>
</b-container>
</template>
<script>
const MAX_EVENT_LOG = 30;
export function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let id = 0;
export default {
data() {
return {
eventLog: []
};
},
created() {
let it = 0;
for (let i = 0; i < 26; i++)
this.pushEvent({
title: `ADDED: ${it++}`,
variant: [undefined, "warning", "success", "danger", "info"][
getRandomInt(0, 4)
]
});
setInterval(() => {
this.pushEvent({
title: `E: ${it++}`,
variant: [undefined, "warning", "success", "danger", "info"][
getRandomInt(0, 4)
]
});
}, 800);
},
methods: {
pushEvent(event) {
if (this.eventLog.length > MAX_EVENT_LOG) this.eventLog.shift();
event.time = new Date();
event.id = id++;
this.eventLog.push(event);
}
}
};
</script>
<style>
.list-enter-active,
.list-leave-active,
.list-move {
transition: opacity 1s, transform 0.5s, translate 0.5s;
}
.list-leave-active {
position: absolute;
transition: opacity 0.4s, transform 0.5s, translate 0.5s;
}
.list-enter {
opacity: 0;
transform: translateY(100%);
}
.list-leave-to {
opacity: 0;
transform: translateY(-100%);
}
.list-enter-to {
opacity: 1;
}
.scroll-container {
overflow-y: auto;
overscroll-behavior-y: contain;
scroll-snap-type: y proximity;
height: 75vh;
background: pink;
}
</style>
请参见示例代码笔:https://codepen.io/raldone01/pen/yLeyjaP
在实际的应用程序中,eventLog是一个计算的属性,来自vuex存储。
编辑:我更新了代码笔。我在笔中添加了vue chat滚动源代码并进行了更改。这些项目现在保持在同一位置,但与动画不匹配,因此看起来确实有些毛刺。
也许不要使用vue-chat-scroll来拥有自己的滚动行为。
删除vue-chat-scroll后,您可以执行以下操作
document.querySelector('An element at the bottom of the page').scrollTop = document.querySelector('element at the bottom of the page').scrollHeight - document.querySelector('element at the bottom of the page').clientHeight;
编辑
我在代码笔中修改了您的代码。
它停留在底部,并且滚动条不动。
您可以尝试。将其复制并粘贴到您的Codepen中,然后告诉我是否您期望的那样
<template>
<b-container>
<b-row class="invisible">Place holder</b-row>
<b-row cols="1" class=" align-content-start mt-4">
<b-col class="p-0">
<transition-group name="list" tag="ol" class="d-block w-100 p-0">
<li
v-for="event in eventLog"
:key="event.id"
class="list-unstyled w-100"
>
<b-container>
<b-row>
<b-col
cols="auto"
class="rounded p-1 mr-1 bg-secondary text-white mt-1"
>
16:34:35
</b-col>
<b-col
class="rounded p-1 mt-1"
:class="`bg-${event.variant ? event.variant : 'light'}`"
>
{{ event.title }}
</b-col>
</b-row>
</b-container>
</li>
</transition-group>
</b-col>
</b-row>
<b-row class="invisible">Place holder</b-row>
</b-container>
</template>
<script>
// added vue sc
const scrollFunc = (el, pos, smooth) => {
if (typeof el.scroll === "function") {
el.scroll({
top: pos,
behavior: smooth ? "smooth" : "instant"
});
} else {
el.scrollTop = pos;
}
}
const scrollToBottom = (el, smooth) => {
scrollFunc(el, el.scrollHeight, smooth)
};
const vChatScroll = {
bind: (el, binding) => {
let scrolled = false;
el.addEventListener("scroll", (e) => {
scrolled = el.scrollTop + el.clientHeight + 1 < el.scrollHeight;
if (scrolled && el.scrollTop === 0) {
el.dispatchEvent(new Event("v-chat-scroll-top-reached"));
}
});
new MutationObserver((e) => {
let config = binding.value || {};
if (config.enabled === false) return;
if (!scrolled) {
scrollToBottom(el, config.smooth);
} else {
const addedNodes = e[e.length - 1].addedNodes
const addedNode = addedNodes[addedNodes.length -1]
if(addedNode)
scrollFunc(el, el.scrollTop-addedNode.clientHeight, config.smooth)
}
}).observe(el, { childList: true, subtree: true });
},
inserted: (el, binding) => {
const config = binding.value || {};
scrollToBottom(el, config.notSmoothOnInit ? false : config.smooth);
}
};
Vue.directive("chat-scroll", vChatScroll);
const MAX_EVENT_LOG = 30;
export function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let id = 0;
export default {
data() {
return {
eventLog: []
};
},
created() {
let it = 0;
for (let i = 0; i < 26; i++)
this.pushEvent({
title: `ADDED: ${it++}`,
variant: [undefined, "warning", "success", "danger", "info"][
getRandomInt(0, 4)
]
});
setInterval(() => {
this.pushEvent({
title: `E: ${it++}`,
variant: [undefined, "warning", "success", "danger", "info"][
getRandomInt(0, 4)
]
});
}, 800);
},
methods: {
pushEvent(event) {
if (this.eventLog.length > MAX_EVENT_LOG) this.eventLog.shift();
event.time = new Date();
event.id = id++;
this.eventLog.push(event);
}
}
};
</script>
<style>
.list-enter-active,
.list-leave-active,
.list-move {
transition: opacity 1s, transform 0.5s, translate 0.5s;
}
.list-leave-active {
position: absolute;
transition: opacity 0.4s, transform 0.5s, translate 0.5s;
}
.list-enter {
opacity: 0;
transform: translateY(100%);
}
.list-leave-to {
opacity: 0;
transform: translateY(-100%);
}
.list-enter-to {
opacity: 1;
}
.scroll-container {
overflow-y: auto;
overscroll-behavior-y: contain;
scroll-snap-type: y proximity;
height: 75vh;
background: pink;
}
</style>