
The problem of placing continuous content of arbitrary size in a screen, or a window, of fixed sizes, has existed for several decades. Approximately the same amount exists and the best solution to this problem: the element of the graphical interface is the scrollbar.
Under the cat you can find out how in the near future the scroll will work in 2GIS Online.
The system scrolling mechanism is implemented at the level of the basic features of the operating system, so we can safely say that it is always better than js emulation: it is more productive, works independently of JavaScript and implements all the necessary features of the system for different types of devices.
')
The design of the system scrollbar, especially Windows under version 8, is capable of disfiguring a significant part of Internet sites. The fact that not everyone agrees with this is confirmed by the fact that there are a large number of solutions that programmatically change the system scrollbar to
custom .
Now at
2GIS Online (and, accordingly, at
API 2GIS ) we use FleXcroll: it emulates the scrolling mechanism and does not suit us for a number of reasons:
- Not a cross-platform solution (in particular, it does not work well on a Mac)
- Like any emulator, in principle, it has performance problems.
- It does not have a built-in header fixing mechanism.
All these factors made us think over two questions:
- Is there a ready-made solution we need, or is it necessary to create our own?
- Is it possible in principle to preserve the scrolling system mechanism, but completely replace its design?
We have formed the basic requirements for the solution, which should change the visual representation of the scrollbar:
- The scrolling system mechanism must be saved: only its design is subject to correction.
- The total size of all dependencies should be minimized. In the ideal case, they should not be at all. The size of the solution itself should be minimized.
- There should be a mechanism for fixing content headers when they leave the field of view, or a simple interface for adding such a mechanism (for more information about this item, see below).
Restrictions
At the time of this writing, it is possible to customize scrolling more or less flexibly using CSS tools only in browsers on the webkit engine. The colors of the scrollbar can be changed in the Internet Explorer browser. In other browsers, support for scrollbar customization through CSS is completely absent. This is partly due to the hard
w3c position :
There are some things that CSS shouldn't do.
Existing solutions

Of that large number of js libraries, more than half replace the native scrolling mechanism. This means that for the wrapper, the overflow property is set to the value hidden, and the nested container with the content we need changes its absolute position when generating events associated with a scroll (for example, mousewheel). These solutions include:
jScrollPane ,
Scrollbar Paper ,
jQuery Custom Scrollbar plugin ,
FleXcroll ,
Tiny Scrollbar and many others.
With this approach, there are two fundamental flaws: the lack of cross-browser compatibility and the lack of cross-platform. The fact is that the interface of events generated by user actions, with the help of which the user scrolls something, is significantly different from browser to browser: from the point of view of standards, this is a real mess. Moreover, the sequence and logic of the "throwing" of events is seriously different between platforms. For example, trackpads on the Mac platform when scrolling generate Wheel-type events with a greater frequency than the wheel of an ordinary mouse, which leads to an
overly fast scrolling in a number of similar decisions.
It is these shortcomings of scroll emulation that led us to the formulation of the first paragraph of requirements.
Many solutions are initially positioned as jQuery plugins. In a situation when we use jQuery in parts, there is a problem of saving traffic. The problem grows significantly if the plugin has a dependency on a much heavier jQuery UI. This applies, for example, ShortScroll and Vertical Scroll. And also from a number of other libraries: for example, one of the few jQuery plugins that preserve the native scrolling mechanism, Scrollbars, depends on 4 plugins: event.drag, resize, mousehold and mousewheel with a total weight of more than 10 kb.
The third item of requirements is not satisfied by any of the solutions we have found.
Own decision
The solution has two main tasks: 1) hide the system scrollbar and 2) display the custom scrollbar.
For simplicity, we will consider only the vertical scrollbar - in our case, as in most others, only he is needed. In addition, it saves the amount of output code. For the case with horizontal scrollbar, the reasoning is extended by analogy.
First of all, let's build an html structure:
<div class='wrapper' id='wrapper'> <div class='scroller' id='scroller'> <article class='container' id='container'> </article> </div> </div>
where container is, in fact, what we want to scroll; scroller is a block in which the container container is not placed in height, but its overflow-y property is set: scroll, which leads to the appearance of a system scrollbar at its right border; wrapper - a window with a width slightly smaller than that of the scroller and the overflow: hidden property. The width is less than exactly the width of the scroller scroll bar.
Unfortunately, using CSS tools it is impossible to know the exact width of the system scrollbar. For example, the option with setting the width of 125% for scroller and 80% for container does not work, at which, it would seem, the widths of container and wrapper should match exactly. You can make the scroller obviously wider, and the wrapper and container set the same width, but this method is not suitable for rubber layout and creates a bug in webkit browsers (see below).
Let's enter js-variables:
var wrapper = document.getElementById('wrapper'), scroller = document.getElementById('scroller'), container = document.getElementById('container');
Now we can calculate the width of the system scroll bar: scroller.offsetWidth is the scroller width, which includes the border, padding, and also the system scroll bar. If we nullify the border and padding with CSS, and subtract scroller.clientWidth, we get the desired width of the scrollbar in pixels.
In webkit-browsers, there is a feature that forces elements to scroll when text is selected in the horizontal direction, even with overflow-x: hidden. That is, the scroller begins to move horizontally within a smaller wrapper, as a result of which the system scrollbar hidden by us is exposed. Fortunately, in webkit browsers, and only in them, we can zero out the width of the scrollbar with CSS, after which the widths of all three blocks will exactly match and there will be no place for the horizontal scroll:
.scroller::-webkit-scrollbar { width: 0; }
Now let's draw and position the custom scrollbar. To do this, we will minimally complicate the html structure with 1 element, which will be fully responsible for the visual presentation of the scrollbar:
<div class='wrapper' id='wrapper'> <div class='scroller' id='scroller'> <article class='container' id='container'> </article> <div class='scroller__bar'></div> </div> </div>
It is important to note here that our task did not require the drawing of the “up” and “down” buttons, as well as the underlying “track”. However, there are no restrictions for their implementation.
When implementing a dredge of drawn custom scrollbar, the main thing is to prohibit the selection of text within the scrolled content. For this, it is enough to make this bind:
function dontStartSelect() { return false; } function selection(on) { if (on) { $(document).on('selectstart', dontStartSelect); } else { $(document).off('selectstart', dontStartSelect); } } event(bar, 'mousedown', function(e) { e.preventDefault(); selection(false);
As you can see, most of the code is needed for the IE8 browser, which so far, unfortunately, cannot be discounted. Please note that resetting the “clicked” mouse state should occur not only when the mouse button is released, but also when the focus page is lost (blur).
Sticky headers
Some elements inside container should always be visible to the user. For example, it could be the headings of sections of an article, if you clicked on them (this is an additional functionality that goes beyond the scope of the decision), the content would “rewind” to the clicked title.
Neither absolute (relative to the scroller), nor relative (relative to its initial positions) positioning of the headers in its pure form, in this case is not suitable. The first is because of the collapse of the content surrounding the header and the corresponding jerks when scrolling; the second is because of the native unrecoverable transitions in the Internet Explorer browser for scrolling, which lead to the jitter of all fixed headers.
In this regard, the html structure had to be complicated a little more, wrapping all the headers in wrappers, the task of which was to save space under the headers when they were pulled out of the stream during fixation with absolute positioning. In principle, a similar result can be achieved by selecting negative margins for headings and sections to which they relate - then you can do without header wrappers.
With each scroll for each header, you must check the condition:
scroller.scrollTop - H[i].offsetTop > Sum(H[j].offsetHeight, j=0..i-1) scroller.scrollTop - H[i].offsetTop < scroller.clientHeight - Sum(H[j].offsetHeight, j=i..n-1)
where H is an array of header elements; i is the number of the header, varies in the range from 0 to n-1; scroller.scrollTop - virtual distance from the upper border of the container to the upper border of the visible part of the container; H [i] .offsetTop - the distance from the upper boundary of the container to the upper boundary of the header H [i].
The fulfillment of both conditions means that heading i is somewhere in the visible area, does not conflict with other headings in position, and its fixation is not required. Violation of the first condition means that the heading is trying to hide from above, and the second - from the bottom. In both cases, fixation is required.
Throwing a mousewheel event
In webkit browsers, we encountered an unpleasant bug: the mousewheel event did not forward from fixed headers up to the scroller. This created the effect of a breakdown (sudden termination) of a scroll when a fixed header hit the cursor (for example, as a result of the same scroll). That is, the user turns the mouse wheel, the title gets under the cursor, is fixed, and, suddenly, the scroll stops working (more precisely, the whole page starts to scroll).
Fortunately, in webkit browsers (and we only needed to turn to them) there is such an opportunity:
$(headers).on('mousewheel', function(e) { var evt = document.createEvent('WheelEvent'); evt.initWebKitWheelEvent(e.originalEvent.wheelDeltaX, e.originalEvent.wheelDeltaY); scroller.dispatchEvent(evt);
Of course, this is an abbreviated version of the code: it is necessary to check the presence of the corresponding functions and the type of event in order not to generate errors in other browsers.
Example
To minimize the amount of js code and dependencies, an approach was used in which the library is not a jQuery plugin, although it uses it by default.
For example, in the simplest case (without fixing the headers), initialization looks like this:
baron($('.wrapper'), { scroller: '.scroller', container: '.container', bar: '.scroller__bar' });
Moreover, $ ('. Wrapper') can be an array of html objects - each of them is initialized.
If you want to fix headers, limit the top position of the scrollbar, and use jQuery alternatives, initialization is a bit more complicated:
baron($('.test_advanced'), { scroller: '.scroller', container: '.container', bar: '.scroller__bar', barOnCls: '.scroller__bar_state_on',
For the event manager, I had to make a wrapper, since its interface is different in different libraries. In principle, you can wrap native functions such as addEventListener and abandon the specialized event manager.
Testing
The actual versions of Chrome, Firefox, Opera, Safari and Internet Explorer (IE) browsers on Windows, Mac and iOs took part in the testing (only existing versions of browsers were tested, of course). In addition, IE9 and IE8 have been tested. All tests on all browsers pass normally.
The peculiarity of the solution proposed in this article is that even if there are any errors in JavaScript, the scroll will still work, because it is systemic, therefore the risk category only includes browsers of outdated versions of Android and Opera Mini, in which the system scrolls on elements not implemented, or implemented poorly.
Total
The proposed solution allows you to save the system scrolling mechanism, replace its design, completely eliminate the hard dependencies on other libraries, and keep within 998 bytes of compressed code (minified and compressed gzip). In addition to this, there is a mechanism for fixing any elements of scrollable content (for example, headers).
The disadvantages include the lack of a horizontal scrollbar and control controls vertical.
The solution code can be downloaded from
Github .
Demo .
The implementation of this solution is scheduled for early March.