-
Notifications
You must be signed in to change notification settings - Fork 209
Fix/lm sticky sidebar #6304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dadish
wants to merge
10
commits into
trunk
Choose a base branch
from
fix/lm-sticky-sidebar
base: trunk
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Fix/lm sticky sidebar #6304
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
2d0d67a
Learning Mode - Implement sticky sidebar for LM templates: modern, vi…
dadish 19a1512
Learning Mode - Make sidebar's height match video.
dadish 6f2686f
Add some docs/comments for Learning Mode Sticky Sidebar.
dadish 4cbd7b6
Learning Mode - Use empty placeholder for sticky sidebar instead of u…
dadish 66fe8f4
Learning Mode - Get rid of the top margin for sticky sidebar.
dadish b5c8462
Learning Mode - Detect the sticky sidebar from the sidebar block class.
dadish abc52d8
Merge branch 'trunk' into fix/lm-sticky-sidebar
dadish efd2d4c
Merge branch 'trunk' into fix/lm-sticky-sidebar
dadish 76f664b
Fine tune the sticky sidebar. Use `transform: translateY()` for scrol…
dadish d49d886
Merge branch 'trunk' into fix/lm-sticky-sidebar
yscik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| /** | ||
| * External dependencies | ||
| */ | ||
| import debounce from 'lodash/debounce'; | ||
|
|
||
| /** | ||
| * The last scroll top value. | ||
| * | ||
| * @member {number} | ||
| */ | ||
| let lastScrollTop = 0; | ||
|
|
||
| /** | ||
| * Calculates the scroll delta. | ||
| */ | ||
| const getScrollDelta = () => { | ||
| const { scrollTop } = document.documentElement; | ||
| const delta = scrollTop - lastScrollTop; | ||
| lastScrollTop = Math.max( 0, scrollTop ); | ||
| return delta; | ||
| }; | ||
|
|
||
| /** | ||
| * Tells if the sidebar is supposed to be sticky. | ||
| * | ||
| * @return {boolean} True if it is sticky. False otherwise. | ||
| */ | ||
| const isStickySidebar = () => | ||
| !! document.querySelectorAll( '.sensei-course-theme__sidebar--is-sticky' ) | ||
| .length; | ||
|
|
||
| /** | ||
| * The sidebar DOM element. | ||
| * | ||
| * @member {HTMLElement} | ||
| */ | ||
| let sidebar = null; | ||
|
|
||
| /** | ||
| * The header DOM element. | ||
| * | ||
| * @member {HTMLElement} | ||
| */ | ||
| let header = null; | ||
|
|
||
| /** | ||
| * A placeholder for the sidebar. | ||
| * | ||
| * @member {HTMLElement} | ||
| */ | ||
| let sidebarPlaceholder = null; | ||
|
|
||
| /** | ||
| * The featured video DOM element. | ||
| * | ||
| * @member {HTMLElement} | ||
| */ | ||
| let featuredVideo = null; | ||
|
|
||
| /** | ||
| * Populates the DOM elements that we need. | ||
| */ | ||
| const queryDomElements = () => { | ||
| sidebar = document.querySelector( '.sensei-course-theme__sidebar' ); | ||
| header = document.querySelector( '.sensei-course-theme__header' ); | ||
| featuredVideo = document.querySelector( | ||
| '.sensei-course-theme-lesson-video' | ||
| ); | ||
| }; | ||
|
|
||
| /** | ||
| * Sets 'position: fixed' for the sidebar and puts a placeholder in it's original | ||
| * place so the original layout is preserved. We also use the placeholder for sticky | ||
| * sidebar position calculation to determine where to put it in any given time. | ||
| */ | ||
| function preparestickySidebar() { | ||
| if ( ! sidebarPlaceholder ) { | ||
| sidebarPlaceholder = sidebar.cloneNode(); | ||
| sidebarPlaceholder.style.visibility = 'hidden'; | ||
| sidebarPlaceholder.setAttribute( 'aria-hidden', 'true' ); | ||
| sidebar.style.transition = 'none'; | ||
| sidebar.style.position = 'fixed'; | ||
| sidebar.style.marginTop = '0'; | ||
| sidebar.parentElement.prepend( sidebarPlaceholder ); | ||
| } | ||
| const sidebarRect = sidebarPlaceholder.getBoundingClientRect(); | ||
| sidebar.style.top = `0`; | ||
| sidebar.style.left = `${ sidebarRect.left }px`; | ||
| sidebar.style.width = `${ sidebarRect.right - sidebarRect.left }px`; | ||
| sidebar.style.transform = `translateY(${ sidebarRect.top }px)`; | ||
| } | ||
|
|
||
| /** | ||
| * Sidebar bottom margin. | ||
| * | ||
| * @member {number} | ||
| */ | ||
| const SIDEBAR_BOTTOM_MARGIN = 32; | ||
|
|
||
| /** | ||
| * Updates the stickySidebar position. The position of the stickySidebar | ||
| * is relative to the Learning Mode header block. It assumes the header is | ||
| * fixed. | ||
| * | ||
| * @param {boolean} initialPosition True if the sidebar should be positioned | ||
| * for it's initial position given the current | ||
| * state of the scrollbar. Used when user opens | ||
| * the page and the page is scrolled into the middle. | ||
| */ | ||
| function updateSidebarPosition( initialPosition = false ) { | ||
| if ( ! sidebar ) { | ||
| return; | ||
| } | ||
|
|
||
| // Get the current dimensions of the elements. | ||
| const headerRect = header.getBoundingClientRect(); | ||
| const sidebarPlaceholderRect = sidebarPlaceholder.getBoundingClientRect(); | ||
| const sidebarRect = sidebar.getBoundingClientRect(); | ||
|
|
||
| // Calculate required values. | ||
| const delta = getScrollDelta(); | ||
| const sidebarHeight = sidebarRect.bottom - sidebarRect.top; | ||
| const sidebarIsTallerThanViewport = | ||
| sidebarHeight > | ||
| window.innerHeight - ( headerRect.bottom + SIDEBAR_BOTTOM_MARGIN ); | ||
| let sidebarNewTop = sidebarPlaceholderRect.top; | ||
|
|
||
| // If the sidebar is very tall and does not fit into the viewport vertically | ||
| // we scroll the sticky sidebar up until the bottom is reached. Or we scroll | ||
| // the sticky sidebar down until the top of the sidebar is reached. | ||
| if ( sidebarIsTallerThanViewport && ! initialPosition ) { | ||
| sidebarNewTop = sidebarRect.top - delta; | ||
| const sidebarNewBottom = sidebarRect.bottom - delta; | ||
| const sidebarMinTop = sidebarPlaceholderRect.top; | ||
| const sidebarMinBottom = window.innerHeight - SIDEBAR_BOTTOM_MARGIN; | ||
|
|
||
| // The sidebar is moving upwards. | ||
| if ( delta >= 0 ) { | ||
| if ( sidebarNewBottom < sidebarMinBottom ) { | ||
| sidebarNewTop = sidebarMinBottom - sidebarHeight; | ||
| } | ||
|
|
||
| // The sidebar is moving downwards. | ||
| } else { | ||
| if ( sidebarNewTop > headerRect.bottom ) { | ||
| sidebarNewTop = headerRect.bottom; | ||
| } | ||
| if ( sidebarNewTop < sidebarMinTop ) { | ||
| sidebarNewTop = sidebarMinTop; | ||
| } | ||
| } | ||
|
|
||
| // If the sidebar fits into the viewport vertically | ||
| // then we simply stick it below the header when user | ||
| // scrolls it up above the header. | ||
| } else if ( sidebarPlaceholderRect.top <= headerRect.bottom ) { | ||
| sidebarNewTop = headerRect.bottom; | ||
|
|
||
| // By default we position the sticky sidebar on top | ||
| // of the original sidebar. | ||
| } else { | ||
| sidebarNewTop = sidebarPlaceholderRect.top; | ||
| } | ||
|
|
||
| // Need to subtract the sidebar top margin because fixed positioned elements | ||
| // are pushed down by css top margin. | ||
|
|
||
| sidebar.style.transform = `translateY(${ sidebarNewTop }px)`; | ||
| } | ||
|
|
||
| /** | ||
| * Reinitializes the sticky sideber | ||
| */ | ||
| const reinitializeSidebar = debounce( () => { | ||
| preparestickySidebar(); | ||
| updateSidebarPosition( true ); | ||
| }, 500 ); | ||
|
|
||
| /** | ||
| * Makes sure the height of the sidebar is at least the height | ||
| * of the featured video in 'modern' LM template. | ||
| */ | ||
| function syncSidebarSizeWithVideo() { | ||
| if ( featuredVideo && sidebar ) { | ||
| new window.ResizeObserver( () => { | ||
| const videoHeight = featuredVideo.offsetHeight; | ||
| const sidebarHeight = sidebar.offsetHeight; | ||
| if ( | ||
| ! videoHeight || | ||
| ! sidebarHeight || | ||
| sidebarHeight >= videoHeight | ||
| ) { | ||
| return; | ||
| } | ||
| sidebar.style.height = `${ videoHeight }px`; | ||
| reinitializeSidebar(); | ||
| } ).observe( featuredVideo ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Makes the sidebar sticky for relevant LM templates. | ||
| */ | ||
| function setupStickySidebar() { | ||
| if ( ! isStickySidebar() ) { | ||
| return; | ||
| } | ||
|
|
||
| queryDomElements(); | ||
|
|
||
| document.defaultView.addEventListener( 'scroll', () => | ||
| updateSidebarPosition() | ||
| ); | ||
|
|
||
| // eslint-disable-next-line @wordpress/no-global-event-listener | ||
| window.addEventListener( 'resize', reinitializeSidebar ); | ||
|
|
||
| // Make sure sidebar height is not shorter than the video height | ||
| // for `moderm` lm template. | ||
| if ( document.body.classList.contains( 'learning-mode--modern' ) ) { | ||
| syncSidebarSizeWithVideo(); | ||
| } | ||
|
|
||
| reinitializeSidebar(); | ||
| } | ||
|
|
||
| // eslint-disable-next-line @wordpress/no-global-event-listener | ||
| window.addEventListener( 'DOMContentLoaded', setupStickySidebar ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.