Fix feed navigation shortcuts when first article is not 100% visible (#11874)

Co-authored-by: Nick Taylor <nick@iamdeveloper.com>
Co-authored-by: Hariharan <hariharan.s@samsung.com>
This commit is contained in:
Hariharan Sampath 2021-04-14 08:05:50 +05:30 committed by GitHub
parent 956caf69c7
commit 208d788672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 48 additions and 13 deletions

View file

@ -152,7 +152,7 @@ function navigate(
const nextFocusable = nextContainer?.querySelector(focusableSelector);
if (nextFocusable) {
nextFocusable.focus();
if (!isInViewport(nextFocusable, 64)) {
if (!isInViewport({ element: nextFocusable, offsetTop: 64 })) {
window.scrollTo({ top: nextContainer.offsetTop - 64 });
}
}
@ -211,15 +211,27 @@ function getPreviousElement(
}
/**
* Gets the first visible element that matches a selector
* Checks if the first completely visible element is present that matches a selector and returns if it is available
* If that isn't visible, it looks for the partially visible element that is present and returns that
* If no elements visible(like the banner case which could cover the entire viewport), we select the first element from the list
*
* @param {string} selector - The CSS selector
*
* @returns {object} The first visible element
*/
function getFirstVisibleElement(selector) {
const elements = document.querySelectorAll(selector);
return Array.prototype.find.call(elements, (element) =>
isInViewport(element),
const elements = [...document.querySelectorAll(selector)];
const completelyVisibleFirstElement = elements.find((element) =>
isInViewport({ element }),
);
if (completelyVisibleFirstElement) {
return completelyVisibleFirstElement;
}
const partiallyVisibleFirstElement = elements.find((element) =>
isInViewport({ element, allowPartialVisibility: true }),
);
if (partiallyVisibleFirstElement) {
return partiallyVisibleFirstElement;
}
return elements[0];
}

View file

@ -3,22 +3,45 @@
*
* @example
* const element = document.getElementById('element');
* isInViewport(element); // true or false
* isInViewport({element, allowPartialVisibility = true}); // true or false
*
* @param {object} element - The HTML element to check
* @param {HTMLElement} element - The HTML element to check
* @param {number} [offsetTop=0] - Part of the screen to ignore counting from the top
*
* @param {boolean} [allowPartialVisibility=false] - A boolean to flip the check between partial or completely visible in the viewport
* @returns {boolean} isInViewport - true if the element is visible in the viewport
*/
export function isInViewport(element, offsetTop = 0) {
export function isInViewport({
element,
offsetTop = 0,
allowPartialVisibility = false,
}) {
const boundingRect = element.getBoundingClientRect();
const clientHeight =
window.innerHeight || document.documentElement.clientHeight;
const clientWidth = window.innerWidth || document.documentElement.clientWidth;
const topIsInViewport =
boundingRect.top <= clientHeight && boundingRect.top >= offsetTop;
const rightIsInViewport =
boundingRect.right >= 0 && boundingRect.right <= clientWidth;
const bottomIsInViewport =
boundingRect.bottom >= offsetTop && boundingRect.bottom <= clientHeight;
const leftIsInViewport =
boundingRect.left <= clientWidth && boundingRect.left >= 0;
const topIsOutOfViewport = boundingRect.top <= offsetTop;
const bottomIsOutOfViewport = boundingRect.bottom >= clientHeight;
const elementSpansEntireViewport =
topIsOutOfViewport && bottomIsOutOfViewport;
if (allowPartialVisibility) {
return (
(topIsInViewport || bottomIsInViewport || elementSpansEntireViewport) &&
(leftIsInViewport || rightIsInViewport)
);
}
return (
boundingRect.top >= offsetTop &&
boundingRect.left >= 0 &&
boundingRect.bottom <= clientHeight &&
boundingRect.right <= clientWidth
topIsInViewport &&
bottomIsInViewport &&
leftIsInViewport &&
rightIsInViewport
);
}