SSL arrowscrollbox.js
Interaktion und PortierbarkeitJAVA
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// This is loaded into all XUL windows. Wrap in a block to prevent // leaking to window scope.
{ class MozArrowScrollbox extends MozElements.BaseControl { static #startEndVertical = ["top", "bottom"]; static #startEndHorizontal = ["left", "right"];
#scrollButtonUpdatePending = false;
let slot = this.shadowRoot.querySelector("slot");
let overflowObserver = new ResizeObserver(_ => {
let contentSize =
slot.getBoundingClientRect()[this.#verticalMode ? "height" : "width"]; // NOTE(emilio): This should be contentSize > scrollClientSize, but due // to how Gecko internally rounds in those cases, we allow for some // minor differences (the internal Gecko layout size is 1/60th of a // pixel, so 0.02 should cover it).
let overflowing = contentSize - this.scrollClientSize > 0.02; if (overflowing == this.overflowing) { if (overflowing) { // Update scroll buttons' disabled state when the slot or scrollbox // size changes while we were already overflowing. this.#updateScrollButtonsDisabledState(/* aRafCount = */ 1);
} return;
}
window.requestAnimationFrame(() => { this.toggleAttribute("overflowing", overflowing); this.#updateScrollButtonsDisabledState(/* aRafCount = */ 1); this.dispatchEvent( new CustomEvent(overflowing ? "overflow" : "underflow")
);
});
});
overflowObserver.observe(slot);
overflowObserver.observe(this.scrollbox);
}
get lineScrollAmount() { // line scroll amout should be the width (at horizontal scrollbox) or // the height (at vertical scrollbox) of the scrolled elements. // However, the elements may have different width or height. So, // for consistent speed, let's use average width of the elements. var elements = this._getScrollableElements(); return elements.length && this.scrollSize / elements.length;
}
get scrollPosition() { returnthis.scrollbox[this.#verticalMode ? "scrollTop" : "scrollLeft"];
}
get startEndProps() { returnthis.#verticalMode
? MozArrowScrollbox.#startEndVertical
: MozArrowScrollbox.#startEndHorizontal;
}
get isRTLScrollbox() { if (this.#verticalMode) { returnfalse;
} if (!("_isRTLScrollbox" in this)) { this._isRTLScrollbox =
document.defaultView.getComputedStyle(this.scrollbox).direction == "rtl";
} returnthis._isRTLScrollbox;
}
_onButtonClick(event) { if (this._clickToScroll) { this._distanceScroll(event);
}
}
_canScrollToElement(element) { if (element.hidden) { returnfalse;
}
// See if the element is hidden via CSS without the hidden attribute. // If we get only zeros for the client rect, this means the element // is hidden. As a performance optimization, we don't flush layout // here which means that on the fly changes aren't fully supported.
let rect = this._boundsWithoutFlushing(element); return !!(rect.top || rect.left || rect.width || rect.height);
}
ensureElementIsVisible(element, aInstant) { if (!this._canScrollToElement(element)) { return;
}
scrollByIndex(index, aInstant) { if (index == 0) { return;
}
var rect = this.scrollClientRect; var [start, end] = this.startEndProps; var x = index > 0 ? rect[end] + 1 : rect[start] - 1; var nextElement = this._elementFromPoint(x, index); if (!nextElement) { return;
}
var targetElement; if (this.isRTLScrollbox) {
index *= -1;
} while (index < 0 && nextElement) { if (this._canScrollToElement(nextElement)) {
targetElement = nextElement;
}
nextElement = nextElement.previousElementSibling;
index++;
} while (index > 0 && nextElement) { if (this._canScrollToElement(nextElement)) {
targetElement = nextElement;
}
nextElement = nextElement.nextElementSibling;
index--;
} if (!targetElement) { return;
}
var mid, rect; while (low <= high) {
mid = Math.floor((low + high) / 2);
rect = elements[mid].getBoundingClientRect(); if (rect[start] > aX) {
high = mid - 1;
} elseif (rect[end] < aX) {
low = mid + 1;
} else { return elements[mid];
}
}
// There's no element at the requested coordinate, but the algorithm // from above yields an element next to it, in a random direction. // The desired scrolling direction leads to the correct element.
var scrollBack = aEvent.originalTarget == this._scrollButtonUp; var scrollLeftOrUp = this.isRTLScrollbox ? !scrollBack : scrollBack; var targetElement;
if (aEvent.detail == 2) { // scroll by the size of the scrollbox
let [start, end] = this.startEndProps;
let x; if (scrollLeftOrUp) {
x = this.scrollClientRect[start] - this.scrollClientSize;
} else {
x = this.scrollClientRect[end] + this.scrollClientSize;
}
targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);
// the next partly-hidden element will become fully visible, // so don't scroll too far if (targetElement) {
targetElement = scrollBack
? targetElement.nextElementSibling
: targetElement.previousElementSibling;
}
}
if (!targetElement) { // scroll to the first resp. last element
let elements = this._getScrollableElements();
targetElement = scrollBack
? elements[0]
: elements[elements.length - 1];
}
// @param aRafCount how many animation frames we need to wait to get // current layout data from getBoundsWithoutFlushing.
#updateScrollButtonsDisabledState(aRafCount = 2) { if (!this.overflowing) { this.toggleAttribute("scrolledtoend", true); this.toggleAttribute("scrolledtostart", true); this.#scrollButtonUpdatePending = false; return;
}
if (aRafCount) { if (this.#scrollButtonUpdatePending) { return;
}
this.#scrollButtonUpdatePending = true;
let oneIter = () => { if (aRafCount--) { if (this.#scrollButtonUpdatePending && this.isConnected) {
window.requestAnimationFrame(oneIter);
}
} else { this.#updateScrollButtonsDisabledState(0);
}
};
oneIter(); return;
}
this.#scrollButtonUpdatePending = false;
let scrolledToStart = false;
let scrolledToEnd = false;
if (!this.overflowing) {
scrolledToStart = true;
scrolledToEnd = true;
} else {
let isAtEdge = (element, start) => {
let edge = start ? this.startEndProps[0] : this.startEndProps[1];
let scrollEdge = this._boundsWithoutFlushing(this.scrollbox)[edge];
let elementEdge = this._boundsWithoutFlushing(element)[edge]; // This is enough slop (>2/3) so that no subpixel value should // get us confused about whether we reached the end. const EPSILON = 0.7; if (start) { return scrollEdge <= elementEdge + EPSILON;
} return elementEdge <= scrollEdge + EPSILON;
};
disconnectedCallback() { // Release timer to avoid reference cycles. if (this._scrollTimer) { this._scrollTimer.cancel(); this._scrollTimer = null;
}
document.l10n.disconnectRoot(this.shadowRoot);
}
on_wheel(event) { // Don't consume the event if we can't scroll. if (!this.overflowing) { return;
}
const { deltaMode } = event;
let doScroll = false;
let instant;
let scrollAmount = 0; if (this.#verticalMode) {
doScroll = true;
scrollAmount = event.deltaY; if (deltaMode == event.DOM_DELTA_PIXEL) {
instant = true;
}
} else { // We allow vertical scrolling to scroll a horizontal scrollbox // because many users have a vertical scroll wheel but no // horizontal support. // Because of this, we need to avoid scrolling chaos on trackpads // and mouse wheels that support simultaneous scrolling in both axes. // We do this by scrolling only when the last two scroll events were // on the same axis as the current scroll event. // For diagonal scroll events we only respect the dominant axis.
let isVertical = Math.abs(event.deltaY) > Math.abs(event.deltaX);
let delta = isVertical ? event.deltaY : event.deltaX;
let scrollByDelta = isVertical && this.isRTLScrollbox ? -delta : delta;
if (this._prevMouseScrolls.length > 1) { this._prevMouseScrolls.shift();
} this._prevMouseScrolls.push(isVertical);
}
if (doScroll) {
let direction = scrollAmount < 0 ? -1 : 1;
if (deltaMode == event.DOM_DELTA_PAGE) {
scrollAmount *= this.scrollClientSize;
} elseif (deltaMode == event.DOM_DELTA_LINE) { // Try to not scroll by more than one page when using line scrolling, // so that all elements are scrollable.
let lineAmount = this.lineScrollAmount;
let clientSize = this.scrollClientSize; if (Math.abs(scrollAmount * lineAmount) > clientSize) { // NOTE: This still tries to scroll a non-fractional amount of // items per line scrolled.
scrollAmount =
Math.max(1, Math.floor(clientSize / lineAmount)) * direction;
}
scrollAmount *= lineAmount;
} else { // DOM_DELTA_PIXEL, leave scrollAmount untouched.
}
let startPos = this.scrollPosition;
if (!this._isScrolling || this._direction != direction) { this._destination = startPos + scrollAmount; this._direction = direction;
} else { // We were already in the process of scrolling in this direction this._destination = this._destination + scrollAmount;
scrollAmount = this._destination - startPos;
} this.scrollByPixels(scrollAmount, instant);
}
on_touchstart(event) { if (event.touches.length > 1) { // Multiple touch points detected, abort. In particular this aborts // the panning gesture when the user puts a second finger down after // already panning with one finger. Aborting at this point prevents // the pan gesture from being resumed until all fingers are lifted // (as opposed to when the user is back down to one finger). this._touchStart = -1;
} else { this._touchStart =
event.touches[0][this.#verticalMode ? "screenY" : "screenX"];
}
}
on_touchmove(event) { if (event.touches.length == 1 && this._touchStart >= 0) {
let touchPoint =
event.touches[0][this.#verticalMode ? "screenY" : "screenX"];
let delta = this._touchStart - touchPoint; if (Math.abs(delta) > 0) { this.scrollByPixels(delta, true); this._touchStart = touchPoint;
}
event.preventDefault();
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.