[Vuejs]-Move by scrolling 3 elements with 1 event handler

0👍

The following snippet is a modified/simplified version of the source code for Vue Scroll Sync. Depending on the complexity of your needs (and your ability to use third-party dependencies in your codebase), you may want to pull that library directly, rather than hard-coding the component.

A few callouts:

  • Note the use of a uuid to identify the different regions whose scroll position should be synced. This prevents a given scroll event being "double-counted" when the other regions’ scroll positions are updated.
  • In all cases, the onScroll handler is temporarily removed from the divs when the scroll position is being modified. Again, this prevents the "double-counting" behaviors that might impact smooth scrolling.
  • The divs are styled and colored for ease of demoing, but of course, you can copy your own markup and content into the scroll-sync component to achieve the same effect. Deep nesting of DOM elements should still work, because the topNode of the component is computed on mount, and events are emitted/captured on that node.
let uuid = 0;

Vue.component('scroll-sync', {
  name: 'ScrollSync',
  template: document.querySelector("#scroll-sync-template").innerHTML,
  data() {
    return {
      topNode: null
    }
  },
  beforeCreate() {
    this.uuid = uuid.toString();
    uuid += 1;
  },
  mounted() {
    let parent = this.$parent
    while (parent) {
      this.topNode = parent
      parent = this.topNode.$parent
    }

    const vue = this;
    this.topNode.$on('scroll-sync', function(data) {
      if (data.emitter === vue.uuid) {
        return
      }
      const {
        scrollTop,
        scrollHeight,
        clientHeight,
        barHeight
      } = data

      const scrollTopOffset = scrollHeight - clientHeight

      vue.$el.onscroll = null
      if (scrollTopOffset > barHeight) {
        vue.$el.scrollTop = scrollTop;
      }
      window.requestAnimationFrame(() => {
        vue.$el.onscroll = vue.handleScroll
      })
    })
    this.$el.onscroll = this.handleScroll
  },
  methods: {
    handleScroll: function(e) {
      const vue = this
      window.requestAnimationFrame(() => {
        const {
          scrollTop,
          scrollHeight,
          clientHeight,
          offsetHeight
        } = e.target

        this.topNode.$emit('scroll-sync', {
          scrollTop,
          scrollHeight,
          clientHeight,
          barHeight: offsetHeight - clientHeight,
          emitter: vue.uuid
        })
      })
    }
  }
});

Vue.config.productionTip = false;

new Vue({
  el: '#app'
});
.scroll-sync-container {
  height: 100%;
  width: 100%;
  position: relative;
  overflow: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <div style="display:flex;">
    <scroll-sync vertical style="height:500px;width:100px;margin-right:50px;">
      <div style="width:100px;height:1000px;background:linear-gradient(red, green, blue);"></div>
    </scroll-sync>
    <scroll-sync vertical style="height:500px;width:100px;">
      <div style="width:100px;height:2000px;background:linear-gradient(red, green, blue);"></div>
    </scroll-sync>
  </div>
</div>

<div id="scroll-sync-template">
  <div class="scroll-sync-container">
    <slot></slot>
  </div>
</div>

1👍

Approach 1: Instead of scroll events, listen for wheel event.

Mostly users scroll by gestures or mouse wheel. With this you can easily avoid infinite loop (one scroll triggering other and vise-versa). This will not work when the user scrolls by dragging the scroll handle.

Approach 2: Listen for mouseover to know which element is directly under cursor and only use scroll event of that element to sync scrolls.

Leave a comment