From 0436507574704d700a73eccd0b82b9f7d0a6c02e Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Mar 2020 19:24:32 +0100 Subject: [PATCH 1/5] use transparent scrollbars on not hover to emulate scrolbar on hover allows us to get rid of the margin-right hack as well --- res/css/structures/_AutoHideScrollbar.scss | 78 +++++-------------- .../structures/AutoHideScrollbar.js | 57 +------------- 2 files changed, 21 insertions(+), 114 deletions(-) diff --git a/res/css/structures/_AutoHideScrollbar.scss b/res/css/structures/_AutoHideScrollbar.scss index 6e4484157c..3d91293c98 100644 --- a/res/css/structures/_AutoHideScrollbar.scss +++ b/res/css/structures/_AutoHideScrollbar.scss @@ -14,76 +14,34 @@ See the License for the specific language governing permissions and 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 { overflow-x: hidden; overflow-y: auto; + overflow-y: overlay; // where supported -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 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; + &::-webkit-scrollbar { + width: 6px; + height: 6px; + background-color: transparent; } - .mx_AutoHideScrollbar:hover { - overflow-y: auto; + &::-webkit-scrollbar-thumb { + border-radius: 3px; + background-color: 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-color: transparent transparent; scrollbar-width: thin; } -// or fallback for webkit browsers -::-webkit-scrollbar { - width: 6px; - height: 6px; - background-color: $scrollbar-track-color; -} -::-webkit-scrollbar-thumb { - background-color: $scrollbar-thumb-color; - border-radius: 3px; +.mx_AutoHideScrollbar:hover { + &::-webkit-scrollbar { + background-color: $scrollbar-track-color; + } + + &::-webkit-scrollbar-thumb { + background-color: $scrollbar-thumb-color; + } + scrollbar-color: $scrollbar-thumb-color $scrollbar-track-color; } diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js index 3f27f51f18..398b07e2d4 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.js @@ -18,7 +18,7 @@ import React from "react"; // derived from code from github.com/noeldelgado/gemini-scrollbar // Copyright (c) Noel Delgado (pixelia.me) -function getScrollbarWidth(alternativeOverflow) { +function getScrollbarWidth() { const div = document.createElement('div'); div.className = 'mx_AutoHideScrollbar'; //to get width of css scrollbar div.style.position = 'absolute'; @@ -26,9 +26,6 @@ function getScrollbarWidth(alternativeOverflow) { 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); @@ -39,18 +36,7 @@ function getScrollbarWidth(alternativeOverflow) { 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); - } + document.body.classList.add("mx_scrollbar_noautohide"); } } @@ -67,42 +53,7 @@ const installBodyClassesIfNeeded = (function() { 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); - 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) { @@ -126,9 +77,7 @@ export default class AutoHideScrollbar extends React.Component { onScroll={this.props.onScroll} onWheel={this.props.onWheel} > -
- { this.props.children } -
+ { this.props.children } ); } } From 75cbc1dc07627a7c82e080bce8994e91fd6734a9 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Mar 2020 19:26:15 +0100 Subject: [PATCH 2/5] Adjust IndicatorScrollbar to not rely on AutoHideScrollbar for overflow --- .../structures/IndicatorScrollbar.js | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/structures/IndicatorScrollbar.js b/src/components/structures/IndicatorScrollbar.js index f14d99f730..05ad4f7561 100644 --- a/src/components/structures/IndicatorScrollbar.js +++ b/src/components/structures/IndicatorScrollbar.js @@ -66,6 +66,22 @@ export default class IndicatorScrollbar extends React.Component { 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() { const hasTopOverflow = this._scrollElement.scrollTop > 0; const hasBottomOverflow = this._scrollElement.scrollHeight > @@ -95,10 +111,6 @@ export default class IndicatorScrollbar extends React.Component { this._scrollElement.classList.remove("mx_IndicatorScrollbar_rightOverflow"); } - if (this._autoHideScrollbar) { - this._autoHideScrollbar.checkOverflow(); - } - if (this.props.trackHorizontalOverflow) { this.setState({ // Offset from absolute position of the container From 2186023e4334d5121a27f038cc1b3c205d5c4820 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Mar 2020 19:26:59 +0100 Subject: [PATCH 3/5] css adjustments because mx_AutoHideScrollBar_offset was removed --- res/css/structures/_GroupView.scss | 4 ++-- res/css/structures/_RoomSubList.scss | 22 +++------------------- res/css/views/rooms/_RoomBreadcrumbs.scss | 2 +- 3 files changed, 6 insertions(+), 22 deletions(-) diff --git a/res/css/structures/_GroupView.scss b/res/css/structures/_GroupView.scss index 2575169664..72a1132c15 100644 --- a/res/css/structures/_GroupView.scss +++ b/res/css/structures/_GroupView.scss @@ -337,7 +337,7 @@ limitations under the License. display: none; } -.mx_GroupView_body .mx_AutoHideScrollbar_offset > * { +.mx_GroupView_body .mx_AutoHideScrollbar > * { margin: 11px 50px 50px 68px; } @@ -366,7 +366,7 @@ limitations under the License. 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-right: 16px; } diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index be44563cfb..0a8ae00f1e 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -166,8 +166,7 @@ limitations under the License. // overflow indicators .mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll { - &.mx_IndicatorScrollbar_topOverflow::before, - &.mx_IndicatorScrollbar_bottomOverflow::after { + &.mx_IndicatorScrollbar_topOverflow::before { position: sticky; left: 0; right: 0; @@ -178,29 +177,14 @@ limitations under the License. pointer-events: none; } - &.mx_IndicatorScrollbar_topOverflow > .mx_AutoHideScrollbar_offset { + + &.mx_IndicatorScrollbar_topOverflow { 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; background: linear-gradient(to top, $panel-gradient); } - - /* - // for now, we remove the bottomOverflow entirely as we don't want to - // lose the screen real-estate due to a bg-colored gradient, but we also - // 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)); - } - */ } diff --git a/res/css/views/rooms/_RoomBreadcrumbs.scss b/res/css/views/rooms/_RoomBreadcrumbs.scss index 67350aac34..3858d836e6 100644 --- a/res/css/views/rooms/_RoomBreadcrumbs.scss +++ b/res/css/views/rooms/_RoomBreadcrumbs.scss @@ -41,7 +41,7 @@ limitations under the License. overflow-x: visible; } - .mx_AutoHideScrollbar_offset { + .mx_AutoHideScrollbar { display: flex; flex-direction: row; height: 100%; From aed85a2183eabdf64cc2cf2eeb2d89a613db1da8 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 25 Mar 2020 19:33:55 +0100 Subject: [PATCH 4/5] fix style lint --- res/css/structures/_RoomSubList.scss | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/res/css/structures/_RoomSubList.scss b/res/css/structures/_RoomSubList.scss index 0a8ae00f1e..1934e681c2 100644 --- a/res/css/structures/_RoomSubList.scss +++ b/res/css/structures/_RoomSubList.scss @@ -168,23 +168,20 @@ limitations under the License. .mx_RoomSubList:not(.resized-all) > .mx_RoomSubList_scroll { &.mx_IndicatorScrollbar_topOverflow::before { position: sticky; + content: ""; + top: 0; left: 0; right: 0; height: 8px; - content: ""; - display: block; z-index: 100; + display: block; pointer-events: none; + transition: background-image 0.1s ease-in; + background: linear-gradient(to top, $panel-gradient); } &.mx_IndicatorScrollbar_topOverflow { margin-top: -8px; } - - &.mx_IndicatorScrollbar_topOverflow::before { - top: 0; - transition: background-image 0.1s ease-in; - background: linear-gradient(to top, $panel-gradient); - } } From 785d0e99fb29d74c25ab8605a2686fa443ef4017 Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Thu, 26 Mar 2020 11:05:27 +0100 Subject: [PATCH 5/5] remove some remaining dead code --- .../structures/AutoHideScrollbar.js | 35 +------------------ 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/src/components/structures/AutoHideScrollbar.js b/src/components/structures/AutoHideScrollbar.js index 398b07e2d4..04323bb548 100644 --- a/src/components/structures/AutoHideScrollbar.js +++ b/src/components/structures/AutoHideScrollbar.js @@ -1,5 +1,6 @@ /* Copyright 2018 New Vector Ltd +Copyright 2020 The Matrix.org Foundation C.I.C. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -16,40 +17,6 @@ limitations under the License. import React from "react"; -// derived from code from github.com/noeldelgado/gemini-scrollbar -// Copyright (c) Noel Delgado (pixelia.me) -function getScrollbarWidth() { - 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"; - 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) { - document.body.classList.add("mx_scrollbar_noautohide"); - } -} - -const installBodyClassesIfNeeded = (function() { - let installed = false; - return function() { - if (!installed) { - install(); - installed = true; - } - }; -})(); - export default class AutoHideScrollbar extends React.Component { constructor(props) { super(props);