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 thetopNode
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.
Source:stackexchange.com