Merge pull request #4276 from matrix-org/bwindels/transparentscrollbars

Fix: allow scrolling while window is not focused & remove scrollbar hack
This commit is contained in:
Bruno Windels 2020-03-27 10:16:53 +00:00 committed by GitHub
commit a2737899c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 46 additions and 179 deletions

View file

@ -14,76 +14,34 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
/* This file has CSS for both native and non-native scrollbars in an order
* that's fairly logical to read but duplicates a selector to separate the
* hiding/showing from the sizing.
*/
/* stylelint-disable no-duplicate-selectors */
/*
1. for browsers that support native overlay auto-hiding scrollbars
*/
.mx_AutoHideScrollbar { .mx_AutoHideScrollbar {
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
overflow-y: overlay; // where supported
-ms-overflow-style: -ms-autohiding-scrollbar; -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 { &::-webkit-scrollbar {
overflow-y: overlay; width: 6px;
} height: 6px;
/* background-color: transparent;
3. as a last fallback, compensate for the scrollbar taking up space in the layout
by having giving the child element (.mx_AutoHideScrollbar_offset) a
negative right margin of the width of the scrollbar when the container
is overflowing. This is what Firefox ends up using. Overflow is detected
in javascript, and adds the mx_AutoHideScrollbar_overflow class to the container.
This only works in Firefox, which should be fine as this fallback is only needed there.
*/
body.mx_scrollbar_nooverlay {
.mx_AutoHideScrollbar {
overflow-y: hidden;
} }
.mx_AutoHideScrollbar:hover { &::-webkit-scrollbar-thumb {
overflow-y: auto; border-radius: 3px;
background-color: transparent;
} }
/* scrollbar-color: transparent transparent;
offset scrollbar width with negative margin-right
include before and after psuedo-elements here so they can
be used to do something interesting like scroll-indicating
gradients (see IndicatorScrollBar)
*/
.mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow > .mx_AutoHideScrollbar_offset,
.mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::before,
.mx_AutoHideScrollbar:hover.mx_AutoHideScrollbar_overflow::after {
margin-right: calc(-1 * var(--scrollbar-width));
}
}
// style the native scrollbars ...
// ... standard css scrollbars (firefox at time of writing)
.mx_AutoHideScrollbar {
scrollbar-color: $scrollbar-thumb-color $scrollbar-track-color;
scrollbar-width: thin; scrollbar-width: thin;
} }
// or fallback for webkit browsers
::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: $scrollbar-track-color;
}
::-webkit-scrollbar-thumb { .mx_AutoHideScrollbar:hover {
background-color: $scrollbar-thumb-color; &::-webkit-scrollbar {
border-radius: 3px; background-color: $scrollbar-track-color;
}
&::-webkit-scrollbar-thumb {
background-color: $scrollbar-thumb-color;
}
scrollbar-color: $scrollbar-thumb-color $scrollbar-track-color;
} }

View file

@ -337,7 +337,7 @@ limitations under the License.
display: none; display: none;
} }
.mx_GroupView_body .mx_AutoHideScrollbar_offset > * { .mx_GroupView_body .mx_AutoHideScrollbar > * {
margin: 11px 50px 50px 68px; margin: 11px 50px 50px 68px;
} }
@ -366,7 +366,7 @@ limitations under the License.
padding: 40px 20px; padding: 40px 20px;
} }
.mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar_offset > :not(.mx_MemberInfo_avatar) { .mx_GroupView .mx_MemberInfo .mx_AutoHideScrollbar > :not(.mx_MemberInfo_avatar) {
padding-left: 16px; padding-left: 16px;
padding-right: 16px; padding-right: 16px;
} }

View file

@ -166,41 +166,22 @@ limitations under the License.
// overflow indicators // overflow indicators
.mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll { .mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll {
&.mx_IndicatorScrollbar_topOverflow::before, &.mx_IndicatorScrollbar_topOverflow::before {
&.mx_IndicatorScrollbar_bottomOverflow::after {
position: sticky; position: sticky;
content: "";
top: 0;
left: 0; left: 0;
right: 0; right: 0;
height: 8px; height: 8px;
content: "";
display: block;
z-index: 100; z-index: 100;
display: block;
pointer-events: none; pointer-events: none;
}
&.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset {
margin-top: -8px;
}
&.mx_IndicatorScrollbar_bottomOverflow > .mx_AutoHideScrollbar_offset {
margin-bottom: -8px;
}
&.mx_IndicatorScrollbar_topOverflow::before {
top: 0;
transition: background-image 0.1s ease-in; transition: background-image 0.1s ease-in;
background: linear-gradient(to top, $panel-gradient); background: linear-gradient(to top, $panel-gradient);
} }
/*
// for now, we remove the bottomOverflow entirely as we don't want to &.mx_IndicatorScrollbar_topOverflow {
// lose the screen real-estate due to a bg-colored gradient, but we also margin-top: -8px;
// don't want to use drop shadows and risk a confusing hierarchy of cards.
// so, instead, we hard-clip at the bottom but soft-clip at the top.
&.mx_IndicatorScrollbar_bottomOverflow::after {
bottom: 0;
transition: background-image 0.1s ease-in;
margin: 0px -8px;
background: linear-gradient(to bottom, rgba(0,0,0,0.1), rgba(0,0,0,0.0));
} }
*/
} }

View file

@ -41,7 +41,7 @@ limitations under the License.
overflow-x: visible; overflow-x: visible;
} }
.mx_AutoHideScrollbar_offset { .mx_AutoHideScrollbar {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 100%; height: 100%;

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2018 New Vector Ltd Copyright 2018 New Vector Ltd
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -16,93 +17,10 @@ limitations under the License.
import React from "react"; 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) {
const div = document.createElement('div');
div.className = 'mx_AutoHideScrollbar'; //to get width of css scrollbar
div.style.position = 'absolute';
div.style.top = '-9999px';
div.style.width = '100px';
div.style.height = '100px';
div.style.overflow = "scroll";
if (alternativeOverflow) {
div.style.overflow = alternativeOverflow;
}
div.style.msOverflowStyle = '-ms-autohiding-scrollbar';
document.body.appendChild(div);
const scrollbarWidth = (div.offsetWidth - div.clientWidth);
document.body.removeChild(div);
return scrollbarWidth;
}
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 { export default class AutoHideScrollbar extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.onOverflow = this.onOverflow.bind(this);
this.onUnderflow = this.onUnderflow.bind(this);
this._collectContainerRef = this._collectContainerRef.bind(this); this._collectContainerRef = this._collectContainerRef.bind(this);
this._needsOverflowListener = null;
}
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");
}
checkOverflow() {
if (!this._needsOverflowListener) {
return;
}
if (this.containerRef.scrollHeight > this.containerRef.clientHeight) {
this.onOverflow();
} else {
this.onUnderflow();
}
}
componentDidUpdate() {
this.checkOverflow();
}
componentDidMount() {
installBodyClassesIfNeeded();
this._needsOverflowListener =
document.body.classList.contains("mx_scrollbar_nooverlay");
this.checkOverflow();
} }
_collectContainerRef(ref) { _collectContainerRef(ref) {
@ -126,9 +44,7 @@ export default class AutoHideScrollbar extends React.Component {
onScroll={this.props.onScroll} onScroll={this.props.onScroll}
onWheel={this.props.onWheel} onWheel={this.props.onWheel}
> >
<div className="mx_AutoHideScrollbar_offset"> { this.props.children }
{ this.props.children }
</div>
</div>); </div>);
} }
} }

View file

@ -66,6 +66,22 @@ export default class IndicatorScrollbar extends React.Component {
this._autoHideScrollbar = autoHideScrollbar; this._autoHideScrollbar = autoHideScrollbar;
} }
componentDidUpdate(prevProps) {
const prevLen = prevProps && prevProps.children && prevProps.children.length || 0;
const curLen = this.props.children && this.props.children.length || 0;
// check overflow only if amount of children changes.
// if we don't guard here, we end up with an infinite
// render > componentDidUpdate > checkOverflow > setState > render loop
if (prevLen !== curLen) {
this.checkOverflow();
}
}
componentDidMount() {
this.checkOverflow();
}
checkOverflow() { checkOverflow() {
const hasTopOverflow = this._scrollElement.scrollTop > 0; const hasTopOverflow = this._scrollElement.scrollTop > 0;
const hasBottomOverflow = this._scrollElement.scrollHeight > const hasBottomOverflow = this._scrollElement.scrollHeight >
@ -95,10 +111,6 @@ export default class IndicatorScrollbar extends React.Component {
this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow"); this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow");
} }
if (this._autoHideScrollbar) {
this._autoHideScrollbar.checkOverflow();
}
if (this.props.trackHorizontalOverflow) { if (this.props.trackHorizontalOverflow) {
this.setState({ this.setState({
// Offset from absolute position of the container // Offset from absolute position of the container