| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- //
- // Traversing
- //
- let smoothDistance, smoothDuration, smoothEnd, smoothStart;
- this.$ = function (selector, el) {
- if (el == null) {
- el = document;
- }
- try {
- return el.querySelector(selector);
- } catch (error) {}
- };
- this.$$ = function (selector, el) {
- if (el == null) {
- el = document;
- }
- try {
- return el.querySelectorAll(selector);
- } catch (error) {}
- };
- $.id = (id) => document.getElementById(id);
- $.hasChild = function (parent, el) {
- if (!parent) {
- return;
- }
- while (el) {
- if (el === parent) {
- return true;
- }
- if (el === document.body) {
- return;
- }
- el = el.parentNode;
- }
- };
- $.closestLink = function (el, parent) {
- if (parent == null) {
- parent = document.body;
- }
- while (el) {
- if (el.tagName === "A") {
- return el;
- }
- if (el === parent) {
- return;
- }
- el = el.parentNode;
- }
- };
- //
- // Events
- //
- $.on = function (el, event, callback, useCapture) {
- if (useCapture == null) {
- useCapture = false;
- }
- if (event.includes(" ")) {
- for (var name of event.split(" ")) {
- $.on(el, name, callback);
- }
- } else {
- el.addEventListener(event, callback, useCapture);
- }
- };
- $.off = function (el, event, callback, useCapture) {
- if (useCapture == null) {
- useCapture = false;
- }
- if (event.includes(" ")) {
- for (var name of event.split(" ")) {
- $.off(el, name, callback);
- }
- } else {
- el.removeEventListener(event, callback, useCapture);
- }
- };
- $.trigger = function (el, type, canBubble, cancelable) {
- if (canBubble == null) {
- canBubble = true;
- }
- if (cancelable == null) {
- cancelable = true;
- }
- const event = document.createEvent("Event");
- event.initEvent(type, canBubble, cancelable);
- el.dispatchEvent(event);
- };
- $.click = function (el) {
- const event = document.createEvent("MouseEvent");
- event.initMouseEvent(
- "click",
- true,
- true,
- window,
- null,
- 0,
- 0,
- 0,
- 0,
- false,
- false,
- false,
- false,
- 0,
- null,
- );
- el.dispatchEvent(event);
- };
- $.stopEvent = function (event) {
- event.preventDefault();
- event.stopPropagation();
- event.stopImmediatePropagation();
- };
- $.eventTarget = (event) => event.target.correspondingUseElement || event.target;
- //
- // Manipulation
- //
- const buildFragment = function (value) {
- const fragment = document.createDocumentFragment();
- if ($.isCollection(value)) {
- for (var child of $.makeArray(value)) {
- fragment.appendChild(child);
- }
- } else {
- fragment.innerHTML = value;
- }
- return fragment;
- };
- $.append = function (el, value) {
- if (typeof value === "string") {
- el.insertAdjacentHTML("beforeend", value);
- } else {
- if ($.isCollection(value)) {
- value = buildFragment(value);
- }
- el.appendChild(value);
- }
- };
- $.prepend = function (el, value) {
- if (!el.firstChild) {
- $.append(value);
- } else if (typeof value === "string") {
- el.insertAdjacentHTML("afterbegin", value);
- } else {
- if ($.isCollection(value)) {
- value = buildFragment(value);
- }
- el.insertBefore(value, el.firstChild);
- }
- };
- $.before = function (el, value) {
- if (typeof value === "string" || $.isCollection(value)) {
- value = buildFragment(value);
- }
- el.parentNode.insertBefore(value, el);
- };
- $.after = function (el, value) {
- if (typeof value === "string" || $.isCollection(value)) {
- value = buildFragment(value);
- }
- if (el.nextSibling) {
- el.parentNode.insertBefore(value, el.nextSibling);
- } else {
- el.parentNode.appendChild(value);
- }
- };
- $.remove = function (value) {
- if ($.isCollection(value)) {
- for (var el of $.makeArray(value)) {
- if (el.parentNode != null) {
- el.parentNode.removeChild(el);
- }
- }
- } else {
- if (value.parentNode != null) {
- value.parentNode.removeChild(value);
- }
- }
- };
- $.empty = function (el) {
- while (el.firstChild) {
- el.removeChild(el.firstChild);
- }
- };
- // Calls the function while the element is off the DOM to avoid triggering
- // unnecessary reflows and repaints.
- $.batchUpdate = function (el, fn) {
- const parent = el.parentNode;
- const sibling = el.nextSibling;
- parent.removeChild(el);
- fn(el);
- if (sibling) {
- parent.insertBefore(el, sibling);
- } else {
- parent.appendChild(el);
- }
- };
- //
- // Offset
- //
- $.rect = (el) => el.getBoundingClientRect();
- $.offset = function (el, container) {
- if (container == null) {
- container = document.body;
- }
- let top = 0;
- let left = 0;
- while (el && el !== container) {
- top += el.offsetTop;
- left += el.offsetLeft;
- el = el.offsetParent;
- }
- return {
- top,
- left,
- };
- };
- $.scrollParent = function (el) {
- while ((el = el.parentNode) && el.nodeType === 1) {
- if (el.scrollTop > 0) {
- break;
- }
- if (["auto", "scroll"].includes(getComputedStyle(el)?.overflowY ?? "")) {
- break;
- }
- }
- return el;
- };
- $.scrollTo = function (el, parent, position, options) {
- if (position == null) {
- position = "center";
- }
- if (options == null) {
- options = {};
- }
- if (!el) {
- return;
- }
- if (parent == null) {
- parent = $.scrollParent(el);
- }
- if (!parent) {
- return;
- }
- const parentHeight = parent.clientHeight;
- const parentScrollHeight = parent.scrollHeight;
- if (!(parentScrollHeight > parentHeight)) {
- return;
- }
- const { top } = $.offset(el, parent);
- const { offsetTop } = parent.firstElementChild;
- switch (position) {
- case "top":
- parent.scrollTop = top - offsetTop - (options.margin || 0);
- break;
- case "center":
- parent.scrollTop =
- top - Math.round(parentHeight / 2 - el.offsetHeight / 2);
- break;
- case "continuous":
- var { scrollTop } = parent;
- var height = el.offsetHeight;
- var lastElementOffset =
- parent.lastElementChild.offsetTop +
- parent.lastElementChild.offsetHeight;
- var offsetBottom =
- lastElementOffset > 0 ? parentScrollHeight - lastElementOffset : 0;
- // If the target element is above the visible portion of its scrollable
- // ancestor, move it near the top with a gap = options.topGap * target's height.
- if (top - offsetTop <= scrollTop + height * (options.topGap || 1)) {
- parent.scrollTop = top - offsetTop - height * (options.topGap || 1);
- // If the target element is below the visible portion of its scrollable
- // ancestor, move it near the bottom with a gap = options.bottomGap * target's height.
- } else if (
- top + offsetBottom >=
- scrollTop + parentHeight - height * ((options.bottomGap || 1) + 1)
- ) {
- parent.scrollTop =
- top +
- offsetBottom -
- parentHeight +
- height * ((options.bottomGap || 1) + 1);
- }
- break;
- }
- };
- $.scrollToWithImageLock = function (el, parent, ...args) {
- if (parent == null) {
- parent = $.scrollParent(el);
- }
- if (!parent) {
- return;
- }
- $.scrollTo(el, parent, ...args);
- // Lock the scroll position on the target element for up to 3 seconds while
- // nearby images are loaded and rendered.
- for (var image of parent.getElementsByTagName("img")) {
- if (!image.complete) {
- (function () {
- let timeout;
- const onLoad = function (event) {
- clearTimeout(timeout);
- unbind(event.target);
- return $.scrollTo(el, parent, ...args);
- };
- var unbind = (target) => $.off(target, "load", onLoad);
- $.on(image, "load", onLoad);
- return (timeout = setTimeout(unbind.bind(null, image), 3000));
- })();
- }
- }
- };
- // Calls the function while locking the element's position relative to the window.
- $.lockScroll = function (el, fn) {
- let parent;
- if ((parent = $.scrollParent(el))) {
- let { top } = $.rect(el);
- if (![document.body, document.documentElement].includes(parent)) {
- top -= $.rect(parent).top;
- }
- fn();
- parent.scrollTop = $.offset(el, parent).top - top;
- } else {
- fn();
- }
- };
- let smoothScroll =
- (smoothStart =
- smoothEnd =
- smoothDistance =
- smoothDuration =
- null);
- $.smoothScroll = function (el, end) {
- smoothEnd = end;
- if (smoothScroll) {
- const newDistance = smoothEnd - smoothStart;
- smoothDuration += Math.min(300, Math.abs(smoothDistance - newDistance));
- smoothDistance = newDistance;
- return;
- }
- smoothStart = el.scrollTop;
- smoothDistance = smoothEnd - smoothStart;
- smoothDuration = Math.min(300, Math.abs(smoothDistance));
- const startTime = Date.now();
- smoothScroll = function () {
- const p = Math.min(1, (Date.now() - startTime) / smoothDuration);
- const y = Math.max(
- 0,
- Math.floor(
- smoothStart +
- smoothDistance * (p < 0.5 ? 2 * p * p : p * (4 - p * 2) - 1),
- ),
- );
- el.scrollTop = y;
- if (p === 1) {
- return (smoothScroll = null);
- } else {
- return requestAnimationFrame(smoothScroll);
- }
- };
- return requestAnimationFrame(smoothScroll);
- };
- //
- // Utilities
- //
- $.makeArray = function (object) {
- if (Array.isArray(object)) {
- return object;
- } else {
- return Array.prototype.slice.apply(object);
- }
- };
- $.arrayDelete = function (array, object) {
- const index = array.indexOf(object);
- if (index >= 0) {
- array.splice(index, 1);
- return true;
- } else {
- return false;
- }
- };
- // Returns true if the object is an array or a collection of DOM elements.
- $.isCollection = (object) =>
- Array.isArray(object) || typeof object?.item === "function";
- const ESCAPE_HTML_MAP = {
- "&": "&",
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "/": "/",
- };
- const ESCAPE_HTML_REGEXP = /[&<>"'\/]/g;
- $.escape = (string) =>
- string.replace(ESCAPE_HTML_REGEXP, (match) => ESCAPE_HTML_MAP[match]);
- const ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g;
- $.escapeRegexp = (string) => string.replace(ESCAPE_REGEXP, "\\$1");
- $.urlDecode = (string) => decodeURIComponent(string.replace(/\+/g, "%20"));
- $.classify = function (string) {
- string = string.split("_");
- for (let i = 0; i < string.length; i++) {
- var substr = string[i];
- string[i] = substr[0].toUpperCase() + substr.slice(1);
- }
- return string.join("");
- };
- //
- // Miscellaneous
- //
- $.noop = function () {};
- $.popup = function (value) {
- try {
- const win = window.open();
- if (win.opener) {
- win.opener = null;
- }
- win.location = value.href || value;
- } catch (error) {
- window.open(value.href || value, "_blank");
- }
- };
- let isMac = null;
- $.isMac = () =>
- isMac != null ? isMac : (isMac = navigator.userAgent.includes("Mac"));
- let isIE = null;
- $.isIE = () =>
- isIE != null
- ? isIE
- : (isIE =
- navigator.userAgent.includes("MSIE") ||
- navigator.userAgent.includes("rv:11.0"));
- let isChromeForAndroid = null;
- $.isChromeForAndroid = () =>
- isChromeForAndroid != null
- ? isChromeForAndroid
- : (isChromeForAndroid =
- navigator.userAgent.includes("Android") &&
- /Chrome\/([.0-9])+ Mobile/.test(navigator.userAgent));
- let isAndroid = null;
- $.isAndroid = () =>
- isAndroid != null
- ? isAndroid
- : (isAndroid = navigator.userAgent.includes("Android"));
- let isIOS = null;
- $.isIOS = () =>
- isIOS != null
- ? isIOS
- : (isIOS =
- navigator.userAgent.includes("iPhone") ||
- navigator.userAgent.includes("iPad"));
- $.overlayScrollbarsEnabled = function () {
- if (!$.isMac()) {
- return false;
- }
- const div = document.createElement("div");
- div.setAttribute(
- "style",
- "width: 100px; height: 100px; overflow: scroll; position: absolute",
- );
- document.body.appendChild(div);
- const result = div.offsetWidth === div.clientWidth;
- document.body.removeChild(div);
- return result;
- };
- const HIGHLIGHT_DEFAULTS = {
- className: "highlight",
- delay: 1000,
- };
- $.highlight = function (el, options) {
- options = { ...HIGHLIGHT_DEFAULTS, ...(options || {}) };
- el.classList.add(options.className);
- setTimeout(() => el.classList.remove(options.className), options.delay);
- };
- $.copyToClipboard = function (string) {
- let result;
- const textarea = document.createElement("textarea");
- textarea.style.position = "fixed";
- textarea.style.opacity = 0;
- textarea.value = string;
- document.body.appendChild(textarea);
- try {
- textarea.select();
- result = !!document.execCommand("copy");
- } catch (error) {
- result = false;
- } finally {
- document.body.removeChild(textarea);
- }
- return result;
- };
|