mirror of https://github.com/vector-im/riot-web
AutoHideScrollbar component
shows scrollbar on hover when needed has fallback to overflow: overlay and fiddling with padding to make content not jump on hover.pull/21833/head
parent
df42ce6689
commit
03781e1327
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
1. for browsers that support native overlay auto-hiding scrollbars
|
||||
*/
|
||||
.mx_AutoHideScrollbar {
|
||||
overflow-y: auto;
|
||||
-ms-overflow-style: -ms-autohiding-scrollbar;
|
||||
}
|
||||
/*
|
||||
2. webkit also supports overflow:overlay where the scrollbars don't take any space
|
||||
in the layout but they don't autohide, so do that only on hover
|
||||
*/
|
||||
body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
body.mx_scrollbar_overlay_noautohide .mx_AutoHideScrollbar:hover {
|
||||
overflow-y: overlay;
|
||||
}
|
||||
/*
|
||||
3. as a last fallback, compensate for the scrollbar taking up space in the layout
|
||||
by playing with the paddings. the default below will add a right padding
|
||||
of the scrollbar width and clear that on hover.
|
||||
this won't work well on classes that also need to set their padding,
|
||||
so this needs to be overriden and adjust the padding with calc like so:
|
||||
```
|
||||
body.mx_scrollbar_nooverlay .componentClass.mx_AutoHideScrollbar_overflow:hover {
|
||||
padding-right: calc(15px - var(--scrollbar-width)) !important;
|
||||
}
|
||||
```
|
||||
*/
|
||||
body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar {
|
||||
box-sizing: border-box;
|
||||
overflow-y: hidden;
|
||||
padding-right: var(--scrollbar-width);
|
||||
}
|
||||
|
||||
body.mx_scrollbar_nooverlay .mx_AutoHideScrollbar:hover {
|
||||
overflow-y: auto;
|
||||
padding-right: 0;
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
Copyright 2018 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
|
||||
// derived from code from github.com/noeldelgado/gemini-scrollbar
|
||||
// Copyright (c) Noel Delgado <pixelia.me@gmail.com> (pixelia.me)
|
||||
function getScrollbarWidth(alternativeOverflow) {
|
||||
var e = document.createElement('div'), sw;
|
||||
e.style.position = 'absolute';
|
||||
e.style.top = '-9999px';
|
||||
e.style.width = '100px';
|
||||
e.style.height = '100px';
|
||||
e.style.overflow = "scroll";
|
||||
if (alternativeOverflow) {
|
||||
e.style.overflow = alternativeOverflow;
|
||||
}
|
||||
e.style.msOverflowStyle = '-ms-autohiding-scrollbar';
|
||||
document.body.appendChild(e);
|
||||
sw = (e.offsetWidth - e.clientWidth);
|
||||
document.body.removeChild(e);
|
||||
return sw;
|
||||
}
|
||||
|
||||
function install() {
|
||||
const scrollbarWidth = getScrollbarWidth();
|
||||
if (scrollbarWidth !== 0) {
|
||||
const hasForcedOverlayScrollbar = getScrollbarWidth('overlay') === 0;
|
||||
// overflow: overlay on webkit doesn't auto hide the scrollbar
|
||||
if (hasForcedOverlayScrollbar) {
|
||||
document.body.classList.add("mx_scrollbar_overlay_noautohide");
|
||||
} else {
|
||||
document.body.classList.add("mx_scrollbar_nooverlay");
|
||||
const style = document.createElement('style');
|
||||
style.type = 'text/css';
|
||||
style.innerText =
|
||||
`body.mx_scrollbar_nooverlay { --scrollbar-width: ${scrollbarWidth}px; }`;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const installBodyClassesIfNeeded = (function() {
|
||||
let installed = false;
|
||||
return function() {
|
||||
if (!installed) {
|
||||
install();
|
||||
installed = true;
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
export default class AutoHideScrollbar extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onOverflow = this.onOverflow.bind(this);
|
||||
this.onUnderflow = this.onUnderflow.bind(this);
|
||||
this._collectContainerRef = this._collectContainerRef.bind(this);
|
||||
}
|
||||
|
||||
onOverflow() {
|
||||
this.containerRef.classList.add("mx_AutoHideScrollbar_overflow");
|
||||
this.containerRef.classList.remove("mx_AutoHideScrollbar_underflow");
|
||||
}
|
||||
|
||||
onUnderflow() {
|
||||
this.containerRef.classList.remove("mx_AutoHideScrollbar_overflow");
|
||||
this.containerRef.classList.add("mx_AutoHideScrollbar_underflow");
|
||||
}
|
||||
|
||||
_collectContainerRef(ref) {
|
||||
if (ref && !this.containerRef) {
|
||||
this.containerRef = ref;
|
||||
const needsOverflowListener =
|
||||
document.body.classList.contains("mx_scrollbar_nooverlay");
|
||||
|
||||
if (needsOverflowListener) {
|
||||
this.containerRef.addEventListener("overflow", this.onOverflow);
|
||||
this.containerRef.addEventListener("underflow", this.onUnderflow);
|
||||
}
|
||||
if (ref.scrollHeight > ref.clientHeight) {
|
||||
this.onOverflow();
|
||||
} else {
|
||||
this.onUnderflow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.containerRef) {
|
||||
this.containerRef.removeEventListener("overflow", this.onOverflow);
|
||||
this.containerRef.removeEventListener("underflow", this.onUnderflow);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
installBodyClassesIfNeeded();
|
||||
return (<div
|
||||
ref={this._collectContainerRef}
|
||||
className={["mx_AutoHideScrollbar", this.props.className].join(" ")}
|
||||
>
|
||||
{ this.props.children }
|
||||
</div>);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue