在删除项目时保持滚动位置

问题描述 投票:0回答:1

我有一个事件日志。各种组件向其中添加事件。如果达到最大值,则将删除最早的事件。添加新事件并将用户滚动到底部时,我使用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滚动源代码并进行了更改。这些项目现在保持在同一位置,但与动画不匹配,因此看起来确实有些毛刺。

javascript vue.js scroll bootstrap-vue
1个回答
0
投票

也许不要使用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>

贷给this post

© www.soinside.com 2019 - 2024. All rights reserved.