Skip to content
Snippets Groups Projects
Commit 5424389b authored by ambient.impact's avatar ambient.impact
Browse files

Issues #3488464 & #2695755: Reworked progress bar to create <progress>:

This also hopefully re-introduces hardware acceleration because I'm
dumb and didn't remember that background-size isn't GPU accelerated.
parent 4cc31e80
No related branches found
No related tags found
No related merge requests found
......@@ -46,7 +46,7 @@
/**
* Progress bar value transition.
*/
--refreshless-progress-bar-value-transition: background-size var(
--refreshless-progress-bar-value-transition: transform var(
--refreshless-progress-bar-transition-duration
) ease-out;
......@@ -101,28 +101,48 @@
z-index: var(--refreshless-progress-bar-z-index);
transition:
var(--refreshless-progress-bar-value-transition),
var(--refreshless-progress-bar-opacity-transition-out);
opacity: 0;
background-color: var(--refreshless-progress-bar-empty-colour);
background-image: linear-gradient(
var(--refreshless-progress-bar-filled-colour),
var(--refreshless-progress-bar-filled-colour)
);
/* Hopefully prevents any unexpected horizontal scrollbars due to translating
the filled portion of the progress bar. */
overflow: hidden;
}
background-size: var(--refreshless-progress-bar-computed-value) 100%;
/* We're using ::before on the container rather than attempting to style and
transition the <progress> element's pseudo-elements because that's still a
bit unreliable even in 2024. (!!!) */
.refreshless-document-progress-bar::before {
content: '';
display: block;
background-repeat: no-repeat;
position: absolute;
will-change: background-size, opacity;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: var(--refreshless-progress-bar-filled-colour);
transition: var(--refreshless-progress-bar-value-transition);
transform: translateX(
calc(var(--refreshless-progress-bar-computed-value) - 100%)
);
}
:root[dir=rtl] .refreshless-document-progress-bar {
background-position-x: right;
:root[dir=rtl] .refreshless-document-progress-bar::before {
transform: translateX(
calc(100% - var(--refreshless-progress-bar-computed-value))
);
}
.refreshless-document-progress-bar--active {
......@@ -130,7 +150,6 @@
opacity: 1;
transition:
var(--refreshless-progress-bar-value-transition),
var(--refreshless-progress-bar-opacity-transition-in);
}
......@@ -17,15 +17,34 @@
elementClass = 'refreshless-document-progress-bar',
) => {
const element = document.createElement(elementType);
const id = 'refreshless-document-progress-bar';
element.classList.add(elementClass);
const container = document.createElement(elementType);
container.classList.add(elementClass);
// This provides the ProgressBar JavaScript class the configured class name
// to build modifier classes from.
element.refreshlessProgressBarClass = elementClass;
container.refreshlessProgressBarClass = elementClass;
const progress = document.createElement('progress');
// There are still (as of 2024 (!!!)) various issues trying to style and
// transition the <progress> element's pseudo-elements across all browsers,
// so the <progress> exists primarily for accessibility reasons. The
// container is used as the visual progress bar, while the <progress>
// element is set as visually hidden.
progress.classList.add(`${elementClass}__progress`, 'visually-hidden');
// The ID is intentionally hard-coded since it's only intended to be used
// for the aria-describedby we set on the <html> element when the progress
// bar is installed.
progress.setAttribute('id', 'refreshless-document-progress-bar');
progress.setAttribute('max', '100');
progress.setAttribute('aria-label', Drupal.t('Page is loading...'));
container.appendChild(progress);
return element;
return container;
}
......@@ -67,11 +86,18 @@
class ProgressBar {
/**
* The progress bar HTML element.
* The progress bar container HTML element.
*
* @type {HTMLElement}
*/
#element;
#containerElement;
/**
* The progress bar <progress> HTML element.
*
* @type {HTMLProgressElement}
*/
#progressElement;
/**
* Whether the progress bar is currently in the processing of hiding.
......@@ -110,7 +136,9 @@
constructor() {
this.#element = Drupal.theme('refreshlessProgressBar');
this.#containerElement = Drupal.theme('refreshlessProgressBar');
this.#progressElement = this.#containerElement.querySelector('progress');
this.setValue(0, false);
......@@ -187,8 +215,8 @@
*/
#setActive(dispatch = true) {
this.#element.classList.add(
`${this.#element.refreshlessProgressBarClass}--active`,
this.#containerElement.classList.add(
`${this.#containerElement.refreshlessProgressBarClass}--active`,
);
if (dispatch === false) {
......@@ -207,8 +235,8 @@
*/
#setInactive(dispatch = true) {
this.#element.classList.remove(
`${this.#element.refreshlessProgressBarClass}--active`,
this.#containerElement.classList.remove(
`${this.#containerElement.refreshlessProgressBarClass}--active`,
);
if (dispatch === false) {
......@@ -277,7 +305,16 @@
this.#setInactive(false);
document.body.insertAdjacentElement('beforebegin', this.#element);
document.body.insertAdjacentElement(
'beforebegin', this.#containerElement,
);
// The progress bar is technically describing the current state of the
// document so mark it as such. This may be helpful to assistive software
// such as screen readers.
html.setAttribute(
'aria-describedby', this.#progressElement.getAttribute('id'),
);
this.refresh().then(async () => {
......@@ -294,13 +331,19 @@
*/
uninstall() {
if (this.#element.parentNode) {
this.#element.remove();
if (this.#containerElement.parentNode) {
this.#containerElement.remove();
}
html.style.removeProperty(durationCustomProperty);
html.style.removeProperty(valueCustomProperty);
if (html.getAttribute(
'aria-describedby',
) === this.#progressElement.getAttribute('id')) {
html.removeAttribute('aria-describedby');
}
}
/**
......@@ -352,6 +395,10 @@
html.style.setProperty(valueCustomProperty, this.#value);
this.#progressElement.setAttribute('value', this.#value * 100);
this.#progressElement.innerText = `${Math.round(this.#value * 100)}%`;
}
/**
......@@ -377,12 +424,12 @@
}
/**
* Get the progress bar HTML element.
* Get the progress bar container HTML element.
*
* @return {HTMLElement}
*/
get element() {
return this.#element;
return this.#containerElement;
}
/**
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment