In some websites that support markdown writing, such as Nuggets or CSDN, the background writing page usually supports instant markdown preview. That is, the whole page is divided into two parts, the left part is the markdown text you input, and the right part is the corresponding preview page. For example, here is an instant preview of the CSDN background writing page markdown:

Instead of this article explaining how to achieve this effect from 0 (and probably a separate article later), look at the left and right container elements in the page body, the Markdown input box element and the preview display box element

This article explores how to scroll one container element while the other scrolls when the contents of both container elements are beyond the container height.


DOMstructure

Since it is related to the scroll bar, then the first thought of js control the height of a scroll bar attribute: scrollTop, as long as you can control the value of this attribute, it is natural to control the scroll bar.

For the following DOM structures:

<div id="container">
  <div class="left"></div>
  <div class="right"></div>
</div>
Copy the code

The. Left element is the left input box container element, the. Right element is the right display box container element, and. Container is their common parent element.

Because overflow scrolling is required, we also need to set the corresponding styles (only the key styles, not all) :

#container {
  display: flex;
  border: 1px solid #bbb;
}
.left..right {
  flex: 1;
  height: 100%;
  word-wrap: break-word;
  overflow-y: scroll;
}
Copy the code

Add enough content to the.left and.right elements to make them appear scrollbars, which looks something like this:

Now that the styles are outlined, you can perform a series of operations on the DOM.


First try

Listen for the scrollTop of two container elements. When one element is rolled, get the scrollTop value of that element and set it to the scrollTop value of the other element.

Such as:

var l=document.querySelector('.left')
var r=document.querySelector('.right')
l.addEventListener('scroll'.function(){
  r.scrollTop = l.scrollTop
})
Copy the code

The effect is as follows:

That seems like a good idea, but now you want not only the right to follow the left, but also the left to follow the right, so add this code:

l.addEventListener('scroll'.function(){
  r.scrollTop = l.scrollTop
})
Copy the code

It looks good, but it’s not that easy.

At this time, when you use the mouse wheel to scroll, but found that the scrolling is a little difficult, the two container elements seem to be blocked by something, it is difficult to scroll.

Careful analysis, the reason is very simple, when you scroll on the left, triggered the left scroll event, so the right follows the scroll, but at the same time, the right follows the scroll, so also triggered the right scroll, so the left must follow the right scroll… Then you get into a kind of mutual trigger situation, so you find that scrolling is very difficult.


To solvescrollThe problem of events firing simultaneously

To solve the above problems, there are temporarily the following two solutions.

willscrollEvent withmousewheelThe event

Since the Scroll event is triggered not only by the active mouse scroll, but also by the scrollTop that changes the container element, the active scroll of the element is actually triggered by the mouse wheel, so we can change the Scroll event to a mouse scroll sensitive event instead of an element scroll sensitive event: ‘mousewheel’, so the above listening code becomes:

l.addEventListener('mousewheel'.function(){
    r.scrollTop = l.scrollTop
})
r.addEventListener('mousewheel'.function(){
    l.scrollTop = r.scrollTop
})
Copy the code

The effect is as follows:

It seems to work, but there are actually two problems.

  • When one container element is rolled, the other container element is also rolled, but the roll is not smooth, and the height has a significant instant bounce

I searched online, but found no content related to the scroll frequency of wheel event. I speculated that this might be a feature of this event

The smallest unit is much smaller than the “Scroll” event. When I scroll on Chrome with my mouse, the distance is exactly 100px each time. This value should be different for different mouse or browser. Gear is fine, so should be less than 100 px, beating would not be so big, my mouse is a company’s own computer, can only use, so the gear scale is larger, but actually really listening is the mouse wheel events through a gear roller card point events, which can explain why will appear the phenomenon of bounce.

In general, every time the mouse wheel passes a cog, it listens for a wheel event. From start to finish, the element that was actively rolled by the mouse has been rolled 100px, so the other container element that followed the roll has been moved 100px

The reason why the above scroll event will not cause instant bounce of the element following the scroll is that when the scrollTop of the element following the scroll changes every time, its value will not have a span as large as 100px, and may not be as small as 1px. However, due to its high trigger frequency and small scroll span, it is at least visually smooth scrolling.

If you want the right scroll to scroll smoothly, you can do it. Every time you listen for the wheel event, regardless of whether it is 100px or 50px worse than the last time, always make the right scroll scroll 10px (or slightly larger or smaller). As long as the scroll is visually smooth and the delay is not too great, it will be 100px to scroll 10 times in a row, and still reach the exact position, as shown in the following code:

function scrollToY(rightELe, toY, step=10) {
    let diff = rightELe.scrollTop - toY
    let realStep = diff > 0 ? -step : step
    if(Math.abs(diff) > step) {
        rightELe.scrollTop = rightELe.scrollTop + realStep
        requestAnimationFrame(()=>{
            scrollToY(rightELe, toY, step)
        })
    } else {
        rightELe.scrollTop = toY
    }
}
Copy the code
  • wheelIt only listens for the mouse wheel event, but if you drag the scroll bar with the mouse, this event is not triggered and other container elements do not follow the scroll

This is very good solve, use the mouse to drag the scroll bar is certainly can trigger the scroll event, and in this case, you must be able to easily determine the by dragging the scrollbar belongs to which container element, only need to deal with the container of scroll event, another with rolling container scroll event processing can not do.

  • wheelEvent compatibility issues

The wheel event is a standard DOM Level3 event, but there are many non-standard events in addition to this event. Different browser kernels use different standards, so it may need to be compatible according to the situation. See MDN MouseWheelEvent for details


Real-time judgment

If you can’t stand the bounce of the wheel, and you’re not sure how far you want to go to follow the scroll box to the right, or if you don’t want to consider compatibility, there’s another way to go, which is still the Scroll event, but with a little extra work.

The problem with the Scroll event is that there is no determination of which container element is actively scrolling, as long as the container element is actively scrolling, it is easy to do. For example, in the above wheel event, the scroll event can be used by dragging the scroll bar with the mouse. The reason is that it is easy to determine which active scroll container element is currently being rolled.

So the key is to figure out which container element is currently actively rolling, and once you solve that, the rest is easy.

Whether the mouse wheel rolling or the mouse by dragging the scroll bar on the scroll bar rolling, will trigger the scroll event, and this time, the Z axis in the coordinate system, must be the coordinates of the cursor is located in the scroll of container elements within the area of, that is to say, on the Z axis, the mouse must be suspended or above in rolling container elements.

When the mouse moves on the screen, it can get the current coordinates of the mouse.

Where clientX and clientY are the coordinates of the current mouse relative to the viewport. It can be considered that as long as the coordinates are within the range of a scroll container, the container element is considered to be an active scroll container element. The coordinate range of the container element can be obtained by using getBoundingClientRect.

Here is sample code for moving the mouse over the.left element:

if(e.clientx > l.left&&e.clientx < L.light&&e.clienty > l.tab) {// enter the.left element}Copy the code

This is fine, but mousemove is a little bit too big for mousemove to listen on because the two scrollcontainer elements take up almost the entire screen area, which is a bit performance demanding, so you can use mouseover instead and just listen to see if the mouse has entered a scrollcontainer element. Also omit the above coordinate judgment.

l.addEventListener('mouseover'.function(){// enter.left scroll inside the container element})Copy the code

When determining which container element is actively rolled by the mouse, only the scroll event of this container needs to be handled, and the scroll event following the scroll container does not need to be handled.

Well, the effect is very good, the performance is also very good, perfect, can call it a day ~

The house!

It’s not that simple!


Roll to scale

All of the above examples work when the contents of two scroll container elements are exactly the same height. What if the contents of two scroll container elements are different heights?

This is the effect:

It can be seen that the contents of the two scrollcontainer elements are different in height, so the largest scrollTop will be different. It will appear that when one element with a smaller scrollTop is rolled to the bottom, the other element is still half rolled, or when one element with a larger scrollTop is rolled to the middle, The other element has already rolled to the bottom.

For example, when you’re writing with Markdown, the height of a level 1 title tag in edit mode is usually less than the height in preview mode, resulting in inconsistent scrolling heights on the left and right sides.

Therefore, if this situation is taken into account, it is not as simple as setting scrollTop values for two scrolling container elements to each other.

Although the height of the contents of the scroll container cannot be fixed, it is certain that the maximum scrolling height of the scroll bar, or the value of scrollTop, must be related to the height of the contents of the scroll container and the height of the scroll container itself.

Because we need to know the height of the contents of the scroll container and the existence of the scroll bar, we need to add a child element to this container element. The height of the child element is unlimited, that is, the height of the contents of the scroll container.

<div id="container">
  <div class="left">
	 <div class="child"></div>
  </div>
  <div class="right">
	  <div class="child"></div>
  </div>
</div>
Copy the code

The following is an example structure:

Through my observation inference and practical verification, I have determined the relationship between them, which is very simple, the most basic addition and subtraction operation:

The maximum scrolling height of the scrollbar (scrollTopMax) = the height of the scrolling container contents (i.e., the child element height CH) - the height of the scrolling container itself (i.e., the container element height pH)Copy the code

namely

In other words, if we have determined the height of the contents of the scroll container (that is, the height of the child element CH) and the height of the scroll container itself (that is, the height of the container element pH), then we must determine the maximum scroll height of the scroll bar (scrollTop), and these two height values are basically available, so we can get the scrollTop

Therefore, if you want two containers of scrolling elements to scroll up and down in equal proportions, i.e. one element can be rolled to the end or bottom, and the other element can be rolled to the end or bottom, you just need to get the scale of the maximum number of scrollTop elements between the two containers.

After determining the scale, when rolling in real time, we only need to get the scrollTop1 of the active rolling container element, and then we can get the corresponding scrollTop2 of another container element following the rolling:

Clear thinking, writing code is very easy, the effect is as follows:

Very smooth ~


summary

For example, if you are writing an online edit and preview page for Markdown, you will need to update the scale value in real time according to the height of the input, but the main body is done, so it is not difficult to make small changes.

In addition, this paper is not only for the following scroll of two scroll container elements, but also can be extended. More following scroll between elements can be realized according to the ideas in this paper. This paper is specific to the two elements for the convenience of explanation.

The simple, runnable code for this article is available on Github, if you’re interested, and don’t forget star