In the current front-end development process, we occasionally meet the requirement of node rolling synchronization. The so-called rolling synchronization is to synchronize the position of the scroll bar of two nodes. When one node is rolled, the other node is also rolled to the corresponding position. More common blog sites that use markdown for article editing often have two sides for real-time editing comparison, which requires a synchronous scroll bar. Less common are data comparison tools that are unlikely to be implemented on the Web, but occasionally you may run into a back-end management project that asks you to create a tabular data comparison tool. This is also the time to take advantage of node rolling synchronization. But in the title of this article we added the word linkage. What does that mean? Let’s read on.

Antecedents feed

First of all, the solution of rolling synchronization has been thoroughly analyzed by predecessors. This article aims to solve the problems of rolling synchronization in more details and make the synchronization event more perfect. If you just want to achieve the rolling synchronization of two nodes, You can refer to the author @Qingnight who published this very detailed nuggets article “Native JS controls multiple scroll bars synchronously follow scroll” long ago. Let’s make a long story short and summarize our requirements first.

In general, if you want to synchronize scrolling, such as dom1 and dom2, you might first come up with the following code:

    <div class="dom1">... </div> <div class="dom2">... </div>let dom1 = document.querySelector('.dom1'),
        dom2 = document.querySelector('.dom2')
Copy the code
    dom1.addEventListener('scroll'.function () {
        let top = this.scrollTop,
            left = this.scrollLeft;
        dom2.scrollTo(left, top)
    })
    dom2.addEventListener('scroll'.function () {
        let top = this.scrollTop,
            left = this.scrollLeft;
        dom1.scrollTo(left, top)
    })
Copy the code

So when you’re executing code on the page, you’ll notice that you can actually synchronize scrolling by dragging the mouse over the scroll bar, but when you’re using the scroll wheel, it’s kind of hard to scroll down. This is because the rolling event of DOM1 drives the rolling of Dom2, and the rolling event of Dom2 drives the rolling of Dom1. The two sides will always synchronize with each other and block each other.

Common Solutions

To solve this problem, the most common method is to use mouseEnter to annotate the nodes. The annotated nodes are the nodes that the mouse triggers the scroll. We simply make the nodes that are not triggered by the mouse not synchronize, as shown in the following method

    let sign = void 0 // Public annotations
    const addScrollEvent = (node, eventFn) = > {
        if(! eventFn)return
        node.addEventListener('mouseenter', e => sign = node.className) // Different nodes use different class values
        let event = eventFn.bind(node)
        node.addEventListener('scroll', event)
    }
    
    addScrollEvent(dom1, function (e) {
        if(sign ! = =this.className) return // If the scroll is not triggered by itself, return directly
        let top = this.scrollTop,
            left = this.scrollLeft;
        dom2.scrollTo(left, top)
    })
    
    addScrollEvent(dom2, function (e) {
        if(sign ! = =this.className) return
        let top = this.scrollTop,
            left = this.scrollLeft;
        dom1.scrollTo(left, top)
    })
Copy the code

This method is abstracted as a common method for reusability, but the Mousewheel scheme obviously can’t be used to implement such a function, so we won’t go over it again.

The problem

Below we’re going to say this article main discussion points, this method also missing, this more than the problem of the missing articles start demand data contrast, similar to the form data contrast to see, comparing two multi-function form view, everyone is using this form, support fixed columns, fixed header, Then the table fixed the left column and fixed the right column and the main table scroll synchronization.

First of all, I’m not sure how some UI frameworks work with table scroll synchronization. They might be like Element UI, where a fixed column is an exact copy of all the row and column nodes of the main table, and then three tables are stacked on top of each other. Use visibility: hidden to undefined column data in a fixed column table; Hide it. I have to say that such an implementation can be extremely slow in the case of large data volumes.

If we implement scroll synchronization of the main table and fixed columns according to the scheme above (annotating with mouse event trigger), the problem solved in this article may occur, because our scroll synchronization needs mouse event as the premise. If we want to synchronize two table scrollbars with fixed columns, we might think

First, find the scroll bar bodies of the two tables, bind them to scroll events, synchronize the scroll bar positions of the two tables using the method above, and then they will synchronize their own fixed columns, and you should be done. As shown in the figure below

Let’s simplify the process, as shown in the figure. If there are six divs representing the left and right of two tables, we use the above method to synchronize the scroll bars of each table, as shown in the figure below

We then, as an outside user, synchronize the main table portion of the two tables, the scrollbar body in the middle of the DEMO, using the same method

addScrollEvent(tableMain1, function () {
    if(sign ! = =this.className) return
    let left = this.scrollLeft,
        top = this.scrollTop
    tableMain2.scrollTo(left, top)
})

addScrollEvent(tableMain2, function () {
    if(sign ! = =this.className) return
    let left = this.scrollLeft,
        top = this.scrollTop
    tableMain1.scrollTo(left, top)
})
Copy the code

Then you realize that it’s not as easy as you thought, and here’s the problem.

As you can see, we can’t synchronize all the nodes in this way.

Why is that?

The reason is simple, because the way we implement scrolling synchronization requires a common premise, a mouseEnter or mouseover event to make an annotation, and when another table does that, Scroll bar synchronization for the three columns of the table requires that any of the three columns trigger a mouse event to execute their synchronization code. As shown below:

The solution

The range of scrolling synchronization events

Now that we know why this is happening, it means that in order to do scroll synchronization we can’t use mouseEnter or mouseover to do annotation schemes, which means we have to start with the Scroll event and then end with the Scroll event, At the same time, they can’t trigger each other’s synchronization and block each other’s scrolling. So we can think of it this way, our scroll event synchronization is always through one node that starts scrolling to synchronize the other nodes, which is consistent with the idea of the annotation method, which is just the mouse event to create a variable that represents the origin of the scroll, Then the scroll events of other nodes do not synchronize the scroll events of other nodes as long as the scroll origin is not itself.

So again, we need to abstract out a method that can pass in the node to be synchronized and set a free variable in the method:

    const syncScroller = function () {
        let nodes = Array.prototype.filter.call(arguments, item => item instanceof HTMLElement)
        if(! nodes.length || nodes.length ===1) return
        let sign; // For annotation
        nodes.forEach((ele, index) = > {
            ele.addEventListener('scroll'.function () { // Bind scroll event to each node
            
            });
        });
    })
    // usage
    syncScroller(node1, node2, node3)
Copy the code

So what’s a good label? Can I still use className as in the previous code? Of course not. The question we should be asking is not what is the origin of the scroll, because the node that triggers the scroll event is the origin of the scroll, but when does the synchronization of the scroll end?

This is much like a buying and selling problem, where the origin of the roll is the seller responsible for production and the node that needs to be synchronized is the buyer. As a seller, what conditions do we need to maximize profit? It’s very simple. We produce and sell as much as the buyer wants.

So in this problem, we went into the question of how much need for a buyer, so the label is need the number of synchronous rolling, and the method to resolve the problem after know the mark also is very clear – the origin of the rolling production quantity and drive all nodes, rolling roll on each node after the number minus one, until the end of the zero is.

So the method looks like this:

    const syncScroller = function () {
        let nodes = Array.prototype.filter.call(arguments, item => item instanceof HTMLElement)
        let max = nodes.length
        if(! max || max ===1) return
        let sign = 0; // For annotation
        nodes.forEach((ele, index) = > {
            ele.addEventListener('scroll'.function () { // Bind scroll event to each node
                if(! sign) {// 0 indicates scroll origin
                    sign = max - 1;
                    let top = this.scrollTop
                    let left = this.scrollLeft
                    for (node of nodes) { // Synchronize all nodes except yourself
                        if (node == this) continue; node.scrollTo(left, top); }}else
                -- sign; // When other nodes scroll, subtract one
            });
        });
    })
    // usage
    syncScroller(node1, node2, node3)
Copy the code

The final result

Let’s take a look at the results:

SyncScroller (tableFixedLeft, tableMain, tableFixedRight)letAddScrollEvent (tableMain1, tableMain2)function () {
        if(sign ! == this.className)return
        let left = this.scrollLeft,
            top = this.scrollTop
        tableMain2.scrollTo(left, top)
    })
    
    addScrollEvent(tableMain2, function () {
        if(sign ! == this.className)return
        let left = this.scrollLeft,
            top = this.scrollTop
        tableMain1.scrollTo(left, top)
    })
Copy the code

As shown below:

Very smooth and perfect! You can see that this method is very useful for writing components that require rolling synchronization, and most importantly, our last example demonstrates that using this method in components is equivalent to tying three nodes together, even if the outside world pulls one node out of the middle, it doesn’t matter!

conclusion

In order to solve the table synchronization feature I really want to break my head, the author I went through the entire exetnet scroll synchronization solution, but finally did it myself.

At this time we must know the title of the scroll synchronization linkage two words is what means, but THIS method I have not encountered problems in the current use, if you use my method encountered problems, you can put forward in the comments below the article, I will timely reply to you yo (if I can solve it).

This is all the content of this article to show ~ if you think the quality is good, please click a praise ~

Come on! To Mr!