Introductory remarks
Below, you will find some demos using floatingScroll
, a jQuery plugin providing any lengthy containers on the page with a separate horizontal scrollbar, which does not get out of sight when the entire page is scrolled.
If you are looking for a guide on plugin usage and API please check out this README on GitHub.
#1: Table with collapsible columns
Description
Among other things this example demonstrates one of the use cases for the update
method. Control column visibility by switching the states on corresponding checkboxes.
The table below was copied from whatwg.org for demonstration purposes only, the data presented here can be out of date.
Live demo
Hidden | Text, Search | URL, Telephone | Password | Date and Time, Date, Month, Week, Time | Local Date and Time | Number | Range | Color | Checkbox, Radio Button | File Upload | Submit Button | Image Button | Reset Button, Button | ||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Content attributes | |||||||||||||||
accept | – | – | – | – | – | – | – | – | – | – | – | Yes | – | – | – |
alt | – | – | – | – | – | – | – | – | – | – | – | – | – | Yes | – |
autocomplete | – | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | – | – | – | – | – |
checked | – | – | – | – | – | – | – | – | – | – | Yes | – | – | – | – |
dirname | – | Yes | – | – | – | – | – | – | – | – | – | – | – | – | – |
formaction | – | – | – | – | – | – | – | – | – | – | – | – | Yes | Yes | – |
formenctype | – | – | – | – | – | – | – | – | – | – | – | – | Yes | Yes | – |
formmethod | – | – | – | – | – | – | – | – | – | – | – | – | Yes | Yes | – |
formnovalidate | – | – | – | – | – | – | – | – | – | – | – | – | Yes | Yes | – |
formtarget | – | – | – | – | – | – | – | – | – | – | – | – | Yes | Yes | – |
height | – | – | – | – | – | – | – | – | – | – | – | – | – | Yes | – |
list | – | Yes | Yes | Yes | – | Yes | Yes | Yes | Yes | Yes | – | – | – | – | – |
max | – | – | – | – | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – |
maxlength | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – | – | – | – | – |
min | – | – | – | – | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – |
multiple | – | – | – | Yes | – | – | – | – | – | – | – | Yes | – | – | – |
pattern | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – | – | – | – | – |
placeholder | – | Yes | Yes | Yes | Yes | – | – | Yes | – | – | – | – | – | – | – |
readonly | – | Yes | Yes | Yes | Yes | Yes | Yes | Yes | – | – | – | – | – | – | – |
required | – | Yes | Yes | Yes | Yes | Yes | Yes | Yes | – | – | Yes | Yes | – | – | – |
size | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – | – | – | – | – |
src | – | – | – | – | – | – | – | – | – | – | – | – | – | Yes | – |
step | – | – | – | – | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – |
width | – | – | – | – | – | – | – | – | – | – | – | – | – | Yes | – |
IDL attributes and methods | |||||||||||||||
checked | – | – | – | – | – | – | – | – | – | – | Yes | – | – | – | – |
files | – | – | – | – | – | – | – | – | – | – | – | Yes | – | – | – |
value | default | value | value | value | value | value | value | value | value | value | default/on | filename | default | default | default |
valueAsDate | – | – | – | – | – | Yes | – | – | – | – | – | – | – | – | – |
valueAsNumber | – | – | – | – | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – |
list | – | Yes | Yes | Yes | – | Yes | Yes | Yes | Yes | Yes | – | – | – | – | – |
selectedOption | – | Yes | Yes | Yes† | – | Yes | Yes | Yes | Yes | Yes | – | – | – | – | – |
select() | – | Yes | Yes | – | Yes | – | – | – | – | – | – | – | – | – | – |
selectionStart | – | Yes | Yes | – | Yes | – | – | – | – | – | – | – | – | – | – |
selectionEnd | – | Yes | Yes | – | Yes | – | – | – | – | – | – | – | – | – | – |
selectionDirection | – | Yes | Yes | – | Yes | – | – | – | – | – | – | – | – | – | – |
setSelectionRange() | – | Yes | Yes | – | Yes | – | – | – | – | – | – | – | – | – | – |
stepDown() | – | – | – | – | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – |
stepUp() | – | – | – | – | – | Yes | Yes | Yes | Yes | – | – | – | – | – | – |
Events | |||||||||||||||
input event | – | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | – | – | – | – | – |
change event | – | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | – | – | – |
Demo code
// ⓘ Plugin initialization
$(".fs-whatwg").floatingScroll();
// Handle checkbox state changes and update floating scrollbar
$(".fs-whatwg-cols").on("change", "input[type='checkbox']", function (e) {
var $checkboxes = $(e.delegateTarget).find("input[type='checkbox']"),
index;
if ($checkboxes.filter(":checked").length) {
index = $checkboxes.index(e.target);
if (index > -1) {
$("#fs-whatwg").find("th, td")
.filter(":nth-child(" + (index + 2) + ")")
.toggleClass("hidden");
}
} else {
$checkboxes.prop("checked", true);
$("#fs-whatwg").find("th.hidden, td.hidden").removeClass("hidden");
}
// ⓘ Force scrollbar update programmatically
$(".fs-whatwg").floatingScroll("update");
});
#2: Code listing
Description
The listing below has long lines of code making horizontal scrolling necessary. This example also demonstrates one of the use cases for the destroy
method.
Live demo
and turn on/off floating scrollbar
import $ from "jquery";
class FScroll {
constructor(cont) {
let inst = this;
inst.cont = cont[0];
let scrollBody = cont.closest(".fl-scrolls-body");
if (scrollBody.length) {
inst.scrollBody = scrollBody;
}
inst.sbar = inst.initScroll();
inst.visible = true;
inst.updateAPI(); // recalculate floating scrolls and hide those of them whose containers are out of sight
inst.syncSbar(inst.cont);
inst.addEventHandlers();
}
initScroll() {
let flscroll = $("<div class='fl-scrolls'></div>");
let {cont} = this;
$("<div></div>").appendTo(flscroll).css({width: `${cont.scrollWidth}px`});
return flscroll.appendTo(cont);
}
addEventHandlers() {
let inst = this;
let eventHandlers = inst.eventHandlers = [
{
$el: inst.scrollBody || $(window),
handlers: {
// Don't use `$.proxy()` since it makes impossible event unbinding individually per instance (see the warning at http://api.jquery.com/unbind/)
scroll() {
inst.checkVisibility();
},
resize() {
inst.updateAPI();
}
}
},
{
$el: inst.sbar,
handlers: {
scroll({target}) {
inst.visible && inst.syncCont(target, true);
}
}
},
{
$el: $(inst.cont),
handlers: {
scroll({target}) {
inst.syncSbar(target, true);
},
focusin() {
setTimeout(inst.syncSbar.bind(inst, inst.cont), 0);
},
"update.fscroll"({namespace}) {
if (namespace === "fscroll") { // Check event namespace to ensure that this is not an extraneous event in a bubbling phase
inst.updateAPI();
}
},
"destroy.fscroll"({namespace}) {
if (namespace === "fscroll") {
inst.destroyAPI();
}
}
}
}
];
eventHandlers.forEach(({$el, handlers}) => $el.bind(handlers));
}
checkVisibility() {
let inst = this;
let mustHide = (inst.sbar[0].scrollWidth <= inst.sbar[0].offsetWidth);
if (!mustHide) {
let contRect = inst.cont.getBoundingClientRect();
let maxVisibleY = inst.scrollBody ?
inst.scrollBody[0].getBoundingClientRect().bottom :
window.innerHeight || document.documentElement.clientHeight;
mustHide = ((contRect.bottom <= maxVisibleY) || (contRect.top > maxVisibleY));
}
if (inst.visible === mustHide) {
inst.visible = !mustHide;
// we cannot simply hide a floating scrollbar since its scrollLeft property will not update in that case
inst.sbar.toggleClass("fl-scrolls-hidden");
}
}
syncCont(sender, preventSyncSbar) {
let inst = this;
// Prevents next syncSbar function from changing scroll position
if (inst.preventSyncCont === true) {
inst.preventSyncCont = false;
return;
}
inst.preventSyncSbar = !!preventSyncSbar;
inst.cont.scrollLeft = sender.scrollLeft;
}
syncSbar(sender, preventSyncCont) {
let inst = this;
// Prevents next syncCont function from changing scroll position
if (inst.preventSyncSbar === true) {
inst.preventSyncSbar = false;
return;
}
inst.preventSyncCont = !!preventSyncCont;
inst.sbar[0].scrollLeft = sender.scrollLeft;
}
// Recalculate scroll width and container boundaries
updateAPI() {
let inst = this;
let {cont} = inst;
inst.sbar.width($(cont).outerWidth());
if (!inst.scrollBody) {
inst.sbar.css("left", `${cont.getBoundingClientRect().left}px`);
}
$("div", inst.sbar).width(cont.scrollWidth);
inst.checkVisibility(); // fixes issue #2
}
// Remove a scrollbar and all related event handlers
destroyAPI() {
this.eventHandlers.forEach(({$el, handlers}) => $el.unbind(handlers));
this.eventHandlers = null;
this.sbar.remove();
}
}
$.fn.floatingScroll = function (method = "init") {
if (method === "init") {
this.each((index, el) => new FScroll($(el)));
} else if (FScroll.prototype.hasOwnProperty(`${method}API`)) {
this.trigger(`${method}.fscroll`);
}
return this;
};
$(document).ready(() => {
$("body [data-fl-scrolls]").floatingScroll();
});
Demo code
// ⓘ Plugin initialization
$(".fs-listing").floatingScroll();
// Destroy or re-create floating scrollbar clicking the button
$("#fs-listing-toggle").on("click", function () {
var $scrollOwner = $(".fs-listing");
if ($scrollOwner.hasClass("fs-listing-collapsed")) {
$scrollOwner
.removeClass("fs-listing-collapsed")
// ⓘ Reinitialize
.floatingScroll("init");
} else {
$scrollOwner
.addClass("fs-listing-collapsed")
// ⓘ Destroy and cleanup
.floatingScroll("destroy");
}
});
#3: Floating scrollbar in a popup
Description
This example demonstrates two special use cases:
- floating scrollbar inside a positioned popup;
- vertical floating scrollbar.
Please find out the details of the “custom viewport” mode in the plugin’s docs.
Live demo
Click here to open a popup
Demo code (key parts)
HTML:
<!-- Apply the "fl-scrolls-viewport" class to the popup block -->
<div class="fs-popup fs-popup-hidden fl-scrolls-viewport">
<!-- Apply the "fl-scrolls-body" class to the popup contents block -->
<div class="fl-scrolls-body">
<!-- Our horizontally scrollable container -->
<div id="fs-maze" class="fs-demo fs-maze">
<!-- Horizontally wide contents -->
</div>
</div>
</div>
CSS:
.fs-popup {
height:550px;
padding:10px;
position:fixed; /* must be positioned (relative is the default) */
/* other styles */
}
.fs-popup .fl-scrolls-body {
height:550px; /* same as for parent (the latter should not be scrollable) */
width:100%;
}
.fs-popup .fl-scrolls[data-orientation="horizontal"]:not(.fl-scrolls-hidden) {
bottom:10px; /* same value as the parent’s bottom padding */
left:10px; /* same value as the parent’s left padding */
}
JavaScript:
var orientation = "horizontal"; // or "vertical"
$("#fs-maze").floatingScroll("init", {orientation: orientation});
$("#fs-open-popup").on("click", function (e) {
// Show the popup
$(".fs-popup").toggleClass("fs-popup-hidden");
// ...
// You may need to force scrollbar update when the popup becomes visible
$("#fs-maze").floatingScroll("update");
});
#4 “Unobtrusive” floating scrollbar
Description
This feature makes a floating scrollbar appear only when the mouse pointer hovers over the scrollable container. Refer the plugin’s docs for more details.