diff --git a/hotline.js b/hotline.js deleted file mode 100644 index 21eb4ba..0000000 --- a/hotline.js +++ /dev/null @@ -1,679 +0,0 @@ -"use strict"; - -/** - * Бегущая строка - * - * @description - * Простой, но мощный класс для создания бегущих строк. Поддерживает - * перемещение мышью и прокрутку колесом, полностью настраивается очень гибок - * для настроек в CSS и подразумевается, что отлично индексируется поисковыми роботами. - * Имеет свой препроцессор, благодаря которому можно создавать бегущие строки - * без программирования - с помощью HTML-аттрибутов, а так же возможность - * изменять параметры (data-hotline-* аттрибуты) на лету. Есть возможность вызывать - * события при выбранных действиях для того, чтобы пользователь имел возможность - * дорабатывать функционал без изучения и изменения моего кода - * - * @example - * сonst hotline = new hotline(); - * hotline.step = '-5'; - * hotline.start(); - * - * @todo - * 1. Бесконечный режим - элементы не удаляются если видны на экране (будут дубликаты) - * - * @copyright WTFPL - * @author Arsen Mirzaev Tatyano-Muradovich - */ -class hotline { - // Идентификатор - #id = 0; - - // Оболочка (instanceof HTMLElement) - #shell = document.getElementById("hotline"); - - // Инстанция горячей строки - #instance = null; - - // Перемещение - #transfer = true; - - // Движение - #move = true; - - // Наблюдатель - #observer = null; - - // Наблюдатель - #block = new Set(["events"]); - - // Настраиваемые параметры - transfer = null; - move = null; - delay = 10; - step = 1; - hover = true; - movable = true; - sticky = false; - wheel = false; - delta = null; - vertical = false; - observe = false; - events = new Map([ - ["start", false], - ["stop", false], - ["move", false], - ["move.block", false], - ["move.unblock", false], - ["offset", false], - ["transfer.start", true], - ["transfer.end", true], - ["onmousemove", false] - ]); - - constructor(id, shell) { - // Запись идентификатора - if (typeof id === "string" || typeof id === "number") this.#id = id; - - // Запись оболочки - if (shell instanceof HTMLElement) this.#shell = shell; - } - - start() { - if (this.#instance === null) { - // Нет запущенной инстанции бегущей строки - - // Инициализация ссылки на ядро - const _this = this; - - // Запуск движения - this.#instance = setInterval(function () { - if (_this.#shell.childElementCount > 1) { - // Найдено содержимое бегущей строки (2 и более) - - // Инициализация буфера для временных данных - let buffer; - - // Инициализация данных первого элемента в строке - const first = { - element: (buffer = _this.#shell.firstElementChild), - coords: buffer.getBoundingClientRect() - }; - - if (_this.vertical) { - // Вертикальная бегущая строка - - // Инициализация сдвига у первого элемента (движение) - first.offset = isNaN( - (buffer = parseFloat(first.element.style.marginTop)) - ) - ? 0 - : buffer; - - // Инициализация отступа до второго элемента у первого элемента (разделение) - first.separator = isNaN( - (buffer = parseFloat( - getComputedStyle(first.element).marginBottom - )) - ) - ? 0 - : buffer; - - // Инициализация крайнего с конца ребра первого элемента в строке - first.end = first.coords.y + first.coords.height + first.separator; - } else { - // Горизонтальная бегущая строка - - // Инициализация отступа у первого элемента (движение) - first.offset = isNaN( - (buffer = parseFloat(first.element.style.marginLeft)) - ) - ? 0 - : buffer; - - // Инициализация отступа до второго элемента у первого элемента (разделение) - first.separator = isNaN( - (buffer = parseFloat(getComputedStyle(first.element).marginRight)) - ) - ? 0 - : buffer; - - // Инициализация крайнего с конца ребра первого элемента в строке - first.end = first.coords.x + first.coords.width + first.separator; - } - - if ( - (_this.vertical && - Math.round(first.end) < _this.#shell.offsetTop) || - (!_this.vertical && Math.round(first.end) < _this.#shell.offsetLeft) - ) { - // Элемент (вместе с отступом до второго элемента) вышел из области видимости (строки) - - if ( - (_this.transfer === null && _this.#transfer) || - _this.transfer === true - ) { - // Перенос разрешен - - if (_this.vertical) { - // Вертикальная бегущая строка - - // Удаление отступов (движения) - first.element.style.marginTop = null; - } else { - // Горизонтальная бегущая строка - - // Удаление отступов (движения) - first.element.style.marginLeft = null; - } - - // Копирование первого элемента в конец строки - _this.#shell.appendChild(first.element); - - if (_this.events.get("transfer.end")) { - // Запрошен вызов события: "перемещение в конец" - - // Вызов события: "перемещение в конец" - document.dispatchEvent( - new CustomEvent(`hotline.${_this.#id}.transfer.end`, { - detail: { - element: first.element, - offset: -( - (_this.vertical - ? first.coords.height - : first.coords.width) + first.separator - ) - } - }) - ); - } - } - } else if ( - (_this.vertical && - Math.round(first.coords.y) > _this.#shell.offsetTop) || - (!_this.vertical && - Math.round(first.coords.x) > _this.#shell.offsetLeft) - ) { - // Передняя (движущая) граница первого элемента вышла из области видимости - - if ( - (_this.transfer === null && _this.#transfer) || - _this.transfer === true - ) { - // Перенос разрешен - - // Инициализация отступа у последнего элемента (разделение) - const separator = - (buffer = isNaN( - (buffer = parseFloat( - getComputedStyle(_this.#shell.lastElementChild)[ - _this.vertical ? "marginBottom" : "marginRight" - ] - )) - ) - ? 0 - : buffer) === 0 - ? first.separator - : buffer; - - // Инициализация координат первого элемента в строке - const coords = _this.#shell.lastElementChild.getBoundingClientRect(); - - if (_this.vertical) { - // Вертикальная бегущая строка - - // Удаление отступов (движения) - _this.#shell.lastElementChild.style.marginTop = - -coords.height - separator + "px"; - } else { - // Горизонтальная бегущая строка - - // Удаление отступов (движения) - _this.#shell.lastElementChild.style.marginLeft = - -coords.width - separator + "px"; - } - - // Копирование последнего элемента в начало строки - _this.#shell.insertBefore( - _this.#shell.lastElementChild, - first.element - ); - - // Удаление отступов у второго элемента в строке (движения) - _this.#shell.children[1].style[ - _this.vertical ? "marginTop" : "marginLeft" - ] = null; - - if (_this.events.get("transfer.start")) { - // Запрошен вызов события: "перемещение в начало" - - // Вызов события: "перемещение в начало" - document.dispatchEvent( - new CustomEvent(`hotline.${_this.#id}.transfer.start`, { - detail: { - element: _this.#shell.lastElementChild, - offset: - (_this.vertical ? coords.height : coords.width) + - separator - } - }) - ); - } - } - } else { - // Элемент в области видимости - - if ((_this.move === null && _this.#move) || _this.move === true) { - // Движение разрешено - - // Запись новых координат сдвига - const offset = first.offset + _this.step; - - // Запись сдвига (движение) - _this.offset(offset); - - if (_this.events.get("move")) { - // Запрошен вызов события: "движение" - - // Вызов события: "движение" - document.dispatchEvent( - new CustomEvent(`hotline.${_this.#id}.move`, { - detail: { - from: first.offset, - to: offset - } - }) - ); - } - } - } - } - }, _this.delay); - - if (this.hover) { - // Запрошена возможность останавливать бегущую строку - - // Инициализация сдвига - let offset = 0; - - // Инициализация слушателя события при перемещении элемента в бегущей строке - const listener = function (e) { - // Увеличение сдвига - offset += e.detail.offset ?? 0; - }; - - // Инициализация обработчика наведения курсора (остановка движения) - this.#shell.onmouseover = function (e) { - // Курсор наведён на бегущую строку - - // Блокировка движения - _this.#move = false; - - if (_this.events.get("move.block")) { - // Запрошен вызов события: "блокировка движения" - - // Вызов события: "блокировка движения" - document.dispatchEvent( - new CustomEvent(`hotline.${_this.#id}.move.block`) - ); - } - - if (_this.movable) { - // Запрошена возможность двигать бегущую строку - - _this.#shell.onmousedown = function (onmousedown) { - // Курсор активирован - - // Инициализация слушателей события перемещения элемента в бегущей строке - document.addEventListener( - `hotline.${_this.#id}.transfer.start`, - listener - ); - document.addEventListener( - `hotline.${_this.#id}.transfer.end`, - listener - ); - - // Инициализация буфера для временных данных - let buffer; - - // Инициализация данных первого элемента в строке - const first = { - offset: isNaN( - (buffer = parseFloat( - _this.vertical - ? _this.#shell.firstElementChild.style.marginTop - : _this.#shell.firstElementChild.style.marginLeft - )) - ) - ? 0 - : buffer - }; - - document.onmousemove = function (onmousemove) { - // Курсор движется - - if (_this.vertical) { - // Вертикальная бегущая строка - - // Инициализация буфера местоположения - const from = _this.#shell.firstElementChild.style.marginTop; - const to = - onmousemove.pageY - - (onmousedown.pageY + offset - first.offset); - - // Движение - _this.#shell.firstElementChild.style.marginTop = to + "px"; - - if (_this.events.get("onmousemove")) { - // Запрошен вызов события: "перемещение мышью" - - // Вызов события: "перемещение мышью" - document.dispatchEvent( - new CustomEvent(`hotline.${_this.#id}.onmousemove`, { - detail: { from, to } - }) - ); - } - } else { - // Горизонтальная бегущая строка - - // Инициализация буфера местоположения - const from = _this.#shell.firstElementChild.style.marginLeft; - const to = - onmousemove.pageX - - (onmousedown.pageX + offset - first.offset); - - // Движение - _this.#shell.firstElementChild.style.marginLeft = to + "px"; - - if (_this.events.get("onmousemove")) { - // Запрошен вызов события: "перемещение мышью" - - // Вызов события: "перемещение мышью" - document.dispatchEvent( - new CustomEvent(`hotline.${_this.#id}.onmousemove`, { - detail: { from, to } - }) - ); - } - } - - // Запись курсора - _this.#shell.style.cursor = "grabbing"; - }; - }; - - // Перещапись событий браузера (чтобы не дёргалось) - _this.#shell.ondragstart = null; - - _this.#shell.onmouseup = function () { - // Курсор деактивирован - - // Остановка обработки движения - document.onmousemove = null; - - // Сброс сдвига - offset = 0; - - document.removeEventListener( - `hotline.${_this.#id}.transfer.start`, - listener - ); - document.removeEventListener( - `hotline.${_this.#id}.transfer.end`, - listener - ); - - // Восстановление курсора - _this.#shell.style.cursor = null; - }; - } - }; - - // Инициализация обработчика отведения курсора (остановка движения) - this.#shell.onmouseleave = function (onmouseleave) { - // Курсор отведён от бегущей строки - - if (!_this.sticky) { - // Отключено прилипание - - // Остановка обработки движения - document.onmousemove = null; - - document.removeEventListener( - `hotline.${_this.#id}.transfer.start`, - listener - ); - document.removeEventListener( - `hotline.${_this.#id}.transfer.end`, - listener - ); - - // Восстановление курсора - _this.#shell.style.cursor = null; - } - - // Сброс сдвига - offset = 0; - - // Разблокировка движения - _this.#move = true; - - if (_this.events.get("move.unblock")) { - // Запрошен вызов события: "разблокировка движения" - - // Вызов события: "разблокировка движения" - document.dispatchEvent( - new CustomEvent(`hotline.${_this.#id}.move.unblock`) - ); - } - }; - } - - if (this.wheel) { - // Запрошена возможность прокручивать колесом мыши - - // Инициализация обработчика наведения курсора (остановка движения) - this.#shell.onwheel = function (e) { - // Курсор наведён на бегущую - - // Инициализация буфера для временных данных - let buffer; - - // Перемещение - _this.offset( - (isNaN( - (buffer = parseFloat( - _this.#shell.firstElementChild.style[ - _this.vertical ? "marginTop" : "marginLeft" - ] - )) - ) - ? 0 - : buffer) + - (_this.delta === null - ? e.wheelDelta - : e.wheelDelta > 0 - ? _this.delta - : -_this.delta) - ); - }; - } - } - - if (this.observe) { - // Запрошено наблюдение за изменениями аттрибутов элемента бегущей строки - - if (this.#observer === null) { - // Отсутствует наблюдатель - - // Инициализация ссылки на ядро - const _this = this; - - // Инициализация наблюдателя - this.#observer = new MutationObserver(function (mutations) { - for (const mutation of mutations) { - if (mutation.type === "attributes") { - // Запись параметра в инстанцию бегущей строки - _this.configure(mutation.attributeName); - } - } - - // Перезапуск бегущей строки - _this.restart(); - }); - - // Активация наблюдения - this.#observer.observe(this.#shell, { - attributes: true - }); - } - } else if (this.#observer instanceof MutationObserver) { - // Запрошено отключение наблюдения - - // Деактивация наблюдения - this.#observer.disconnect(); - - // Удаление наблюдателя - this.#observer = null; - } - - if (this.events.get("start")) { - // Запрошен вызов события: "запуск" - - // Вызов события: "запуск" - document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.start`)); - } - - return this; - } - - stop() { - // Остановка бегущей строки - clearInterval(this.#instance); - - // Удаление инстанции интервала - this.#instance = null; - - if (this.events.get("stop")) { - // Запрошен вызов события: "остановка" - - // Вызов события: "остановка" - document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.stop`)); - } - - return this; - } - - restart() { - // Остановка бегущей строки - this.stop(); - - // Запуск бегущей строки - this.start(); - } - - configure(attribute) { - // Инициализация названия параметра - const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1]; - - if (typeof parameter === "string") { - // Параметр найден - - // Проверка на разрешение изменения - if (this.#block.has(parameter)) return; - - // Инициализация значения параметра - const value = this.#shell.getAttribute(attribute); - - // Инициализация буфера для временных данных - let buffer; - - // Запись параметра - this[parameter] = isNaN((buffer = parseFloat(value))) - ? value === "true" - ? true - : value === "false" - ? false - : value - : buffer; - } - - return this; - } - - offset(value) { - // Запись отступа - this.#shell.firstElementChild.style[ - this.vertical ? "marginTop" : "marginLeft" - ] = value + "px"; - - if (this.events.get("offset")) { - // Запрошен вызов события: "сдвиг" - - // Вызов события: "сдвиг" - document.dispatchEvent( - new CustomEvent(`hotline.${this.#id}.offset`, { - detail: { - to: value - } - }) - ); - } - - return this; - } - - static preprocessing(event = false) { - // Инициализация счётчиков инстанций горячей строки - const success = new Set(); - let error = 0; - - for (const element of document.querySelectorAll('*[data-hotline="true"]')) { - // Перебор элементов для инициализации бегущих строк - - if (typeof element.id === "string") { - // Найден идентификатор - - // Инициализация инстанции бегущей строки - const hotline = new this(element.id, element); - - for (const attribute of element.getAttributeNames()) { - // Перебор аттрибутов - - // Запись параметра в инстанцию бегущей строки - hotline.configure(attribute); - } - - // Запуск бегущей строки - hotline.start(); - - // Запись инстанции бегущей строки в элемент - element.hotline = hotline; - - // Запись в счётчик успешных инициализаций - success.add(hotline); - } else ++error; - } - - if (event) { - // Запрошен вызов события: "предварительная подготовка" - - // Вызов события: "предварительная подготовка" - document.dispatchEvent( - new CustomEvent(`hotline.preprocessed`, { - detail: { - success, - error - } - }) - ); - } - } -} - -document.dispatchEvent( - new CustomEvent("hotline.loaded", { - detail: { hotline } - }) -); diff --git a/hotline.mjs b/hotline.mjs new file mode 100644 index 0000000..4016e9b --- /dev/null +++ b/hotline.mjs @@ -0,0 +1,739 @@ +"use strict"; + +/** + * @name hotline.mjs + * + * @description Module for creating "hot lines" + * + * @example + * сonst hotline = new hotline(); + * hotline.step = '-5'; + * hotline.start(); + * + * {@link https://git.mirzaev.sexy/mirzaev/hotline.js Repository} + * + * @license http://www.wtfpl.net/ Do What The Fuck You Want To Public License + * @author Arsen Mirzaev Tatyano-Muradovich + */ +export default class hotline { + // Идентификатор + #id = 0; + + // Оболочка (instanceof HTMLElement) + #shell = document.getElementById("hotline"); + + // Инстанция горячей строки + #instance = null; + + // Перемещение + #transfer = true; + + // Движение + #move = true; + + // Наблюдатель + #observer = null; + + // Реестр запрещённых к изменению параметров + #block = new Set(["events"]); + + // Status (null, active, inactive) + #status = null; + + // Settings + transfer = null; + move = null; + delay = 10; + step = 1; + hover = true; + movable = true; + sticky = false; + wheel = false; + delta = null; + vertical = false; + button = 0; // button for grabbing. 0 is main mouse button (left) + observe = false; + events = new Map([ + ["start", false], + ["stop", false], + ["move", false], + ["move.block", false], + ["move.unblock", false], + ["offset", false], + ["transfer.start", true], + ["transfer.end", true], + ["mousemove", false], + ["touchmove", false], + ]); + + // Is hotline currently moving due to "onmousemove" or "ontouchmove"? + moving = false; + + constructor(id, shell) { + // Запись идентификатора + if (typeof id === "string" || typeof id === "number") this.#id = id; + + // Запись оболочки + if (shell instanceof HTMLElement) this.#shell = shell; + } + + start() { + if (this.#instance === null) { + // Нет запущенной инстанции бегущей строки + + // Инициализация ссылки на ядро + const _this = this; + + // Запуск движения + this.#instance = setInterval(function () { + if (_this.#shell.childElementCount > 1) { + // Найдено содержимое бегущей строки (2 и более) + + // Инициализация буфера для временных данных + let buffer; + + // Инициализация данных первого элемента в строке + const first = { + element: (buffer = _this.#shell.firstElementChild), + coords: buffer.getBoundingClientRect(), + }; + + if (_this.vertical) { + // Вертикальная бегущая строка + + // Инициализация сдвига у первого элемента (движение) + first.offset = isNaN( + buffer = parseFloat(first.element.style.marginTop), + ) + ? 0 + : buffer; + + // Инициализация отступа до второго элемента у первого элемента (разделение) + first.separator = isNaN( + buffer = parseFloat( + getComputedStyle(first.element).marginBottom, + ), + ) + ? 0 + : buffer; + + // Инициализация крайнего с конца ребра первого элемента в строке + first.end = first.coords.y + first.coords.height + + first.separator; + } else { + // Горизонтальная бегущая строка + + // Инициализация отступа у первого элемента (движение) + first.offset = isNaN( + buffer = parseFloat(first.element.style.marginLeft), + ) + ? 0 + : buffer; + + // Инициализация отступа до второго элемента у первого элемента (разделение) + first.separator = isNaN( + buffer = parseFloat( + getComputedStyle(first.element).marginRight, + ), + ) + ? 0 + : buffer; + + // Инициализация крайнего с конца ребра первого элемента в строке + first.end = first.coords.x + first.coords.width + + first.separator; + } + + if ( + (_this.vertical && + Math.round(first.end) < _this.#shell.offsetTop) || + (!_this.vertical && + Math.round(first.end) < _this.#shell.offsetLeft) + ) { + // Элемент (вместе с отступом до второго элемента) вышел из области видимости (строки) + + if ( + (_this.transfer === null && _this.#transfer) || + _this.transfer === true + ) { + // Перенос разрешен + + if (_this.vertical) { + // Вертикальная бегущая строка + + // Удаление отступов (движения) + first.element.style.marginTop = null; + } else { + // Горизонтальная бегущая строка + + // Удаление отступов (движения) + first.element.style.marginLeft = null; + } + + // Копирование первого элемента в конец строки + _this.#shell.appendChild(first.element); + + if (_this.events.get("transfer.end")) { + // Запрошен вызов события: "перемещение в конец" + + // Вызов события: "перемещение в конец" + document.dispatchEvent( + new CustomEvent(`hotline.${_this.#id}.transfer.end`, { + detail: { + element: first.element, + offset: -( + (_this.vertical + ? first.coords.height + : first.coords.width) + first.separator + ), + }, + }), + ); + } + } + } else if ( + (_this.vertical && + Math.round(first.coords.y) > _this.#shell.offsetTop) || + (!_this.vertical && + Math.round(first.coords.x) > _this.#shell.offsetLeft) + ) { + // Передняя (движущая) граница первого элемента вышла из области видимости + + if ( + (_this.transfer === null && _this.#transfer) || + _this.transfer === true + ) { + // Перенос разрешен + + // Инициализация отступа у последнего элемента (разделение) + const separator = (buffer = isNaN( + buffer = parseFloat( + getComputedStyle(_this.#shell.lastElementChild)[ + _this.vertical ? "marginBottom" : "marginRight" + ], + ), + ) + ? 0 + : buffer) === 0 + ? first.separator + : buffer; + + // Инициализация координат первого элемента в строке + const coords = _this.#shell.lastElementChild + .getBoundingClientRect(); + + if (_this.vertical) { + // Вертикальная бегущая строка + + // Удаление отступов (движения) + _this.#shell.lastElementChild.style.marginTop = -coords.height - + separator + "px"; + } else { + // Горизонтальная бегущая строка + + // Удаление отступов (движения) + _this.#shell.lastElementChild.style.marginLeft = -coords.width - + separator + "px"; + } + + // Копирование последнего элемента в начало строки + _this.#shell.insertBefore( + _this.#shell.lastElementChild, + first.element, + ); + + // Удаление отступов у второго элемента в строке (движения) + _this.#shell.children[1].style[ + _this.vertical ? "marginTop" : "marginLeft" + ] = null; + + if (_this.events.get("transfer.start")) { + // Запрошен вызов события: "перемещение в начало" + + // Вызов события: "перемещение в начало" + document.dispatchEvent( + new CustomEvent(`hotline.${_this.#id}.transfer.start`, { + detail: { + element: _this.#shell.lastElementChild, + offset: (_this.vertical ? coords.height : coords.width) + + separator, + }, + }), + ); + } + } + } else { + // Элемент в области видимости + + if ( + (_this.move === null && _this.#move) || _this.move === true + ) { + // Движение разрешено + + // Запись новых координат сдвига + const offset = first.offset + _this.step; + + // Запись сдвига (движение) + _this.offset(offset); + + if (_this.events.get("move")) { + // Запрошен вызов события: "движение" + + // Вызов события: "движение" + document.dispatchEvent( + new CustomEvent(`hotline.${_this.#id}.move`, { + detail: { + from: first.offset, + to: offset, + }, + }), + ); + } + } + } + } + }, _this.delay); + + if (this.hover) { + // Запрошена возможность останавливать бегущую строку + + // Инициализация сдвига + let offset = 0; + + // Инициализация слушателя события при перемещении элемента в бегущей строке + const listener = function (e) { + // Увеличение сдвига + offset += e.detail.offset ?? 0; + }; + + // Объявление переменной в области видимости обработки остановки бегущей строки + let move; + + // Инициализация обработчика наведения курсора (остановка движения) + this.#shell.onmouseover = function (e) { + // Курсор наведён на бегущую строку + + // Блокировка движения + _this.#move = false; + + if (_this.events.get("move.block")) { + // Запрошен вызов события: "блокировка движения" + + // Вызов события: "блокировка движения" + document.dispatchEvent( + new CustomEvent(`hotline.${_this.#id}.move.block`), + ); + } + }; + + if (this.movable) { + // Запрошена возможность двигать бегущую строку + + _this.#shell.onmousedown = + _this.#shell.ontouchstart = + function ( + start, + ) { + // Handling a "mousedown" and a "touchstart" on hotline + + if ( + start.type === "touchstart" || + start.button === _this.button + ) { + const x = start.pageX || start.touches[0].pageX; + const y = start.pageY || start.touches[0].pageY; + + // Блокировка движения + _this.#move = false; + + if (_this.events.get("move.block")) { + // Запрошен вызов события: "блокировка движения" + + // Вызов события: "блокировка движения" + document.dispatchEvent( + new CustomEvent(`hotline.${_this.#id}.move.block`), + ); + } + + // Инициализация слушателей события перемещения элемента в бегущей строке + document.addEventListener( + `hotline.${_this.#id}.transfer.start`, + listener, + ); + document.addEventListener( + `hotline.${_this.#id}.transfer.end`, + listener, + ); + + // Инициализация буфера для временных данных + let buffer; + + // Инициализация данных первого элемента в строке + const first = { + offset: isNaN( + buffer = parseFloat( + _this.vertical + ? _this.#shell.firstElementChild.style + .marginTop + : _this.#shell.firstElementChild.style + .marginLeft, + ), + ) + ? 0 + : buffer, + }; + + move = (move) => { + // Обработка движения курсора + + if (_this.#status === "active") { + // Запись статуса ручного перемещения + _this.moving = true; + + const _x = move.pageX || move.touches[0].pageX; + const _y = move.pageY || move.touches[0].pageY; + + if (_this.vertical) { + // Вертикальная бегущая строка + + // Инициализация буфера местоположения + const from = + _this.#shell.firstElementChild.style.marginTop; + const to = _y - (y + offset - first.offset); + + // Движение + _this.#shell.firstElementChild.style.marginTop = to + + "px"; + } else { + // Горизонтальная бегущая строка + + // Инициализация буфера местоположения + const from = + _this.#shell.firstElementChild.style.marginLeft; + const to = _x - (x + offset - first.offset); + + // Движение + _this.#shell.firstElementChild.style.marginLeft = to + + "px"; + } + + if (_this.events.get(move.type)) { + // Запрошен вызов события: "перемещение" (мышью или касанием) + + // Вызов события: "перемещение" (мышью или касанием) + document.dispatchEvent( + new CustomEvent( + `hotline.${_this.#id}.${move.type}`, + { + detail: { from, to }, + }, + ), + ); + } + + // Запись курсора + _this.#shell.style.cursor = "grabbing"; + } + }; + + // Запуск обработки движения + document.addEventListener("mousemove", move); + document.addEventListener("touchmove", move); + } + }; + + // Перещапись событий браузера (чтобы не дёргалось) + _this.#shell.ondragstart = null; + + _this.#shell.onmouseup = _this.#shell.ontouchend = function () { + // Курсор деактивирован + + // Запись статуса ручного перемещения + _this.moving = false; + + // Остановка обработки движения + document.removeEventListener("mousemove", move); + document.removeEventListener("touchmove", move); + + // Сброс сдвига + offset = 0; + + document.removeEventListener( + `hotline.${_this.#id}.transfer.start`, + listener, + ); + document.removeEventListener( + `hotline.${_this.#id}.transfer.end`, + listener, + ); + + // Разблокировка движения + _this.#move = true; + + if (_this.events.get("move.unblock")) { + // Запрошен вызов события: "разблокировка движения" + + // Вызов события: "разблокировка движения" + document.dispatchEvent( + new CustomEvent(`hotline.${_this.#id}.move.unblock`), + ); + } + + // Восстановление курсора + _this.#shell.style.cursor = null; + }; + } + + // Инициализация обработчика отведения курсора (остановка движения) + this.#shell.onmouseleave = function (onmouseleave) { + // Курсор отведён от бегущей строки + + if (!_this.sticky) { + // Отключено прилипание + + // Запись статуса ручного перемещения + _this.moving = false; + + // Остановка обработки движения + document.removeEventListener("mousemove", move); + document.removeEventListener("touchmove", move); + + document.removeEventListener( + `hotline.${_this.#id}.transfer.start`, + listener, + ); + document.removeEventListener( + `hotline.${_this.#id}.transfer.end`, + listener, + ); + + // Восстановление курсора + _this.#shell.style.cursor = null; + } + + // Сброс сдвига + offset = 0; + + // Разблокировка движения + _this.#move = true; + + if (_this.events.get("move.unblock")) { + // Запрошен вызов события: "разблокировка движения" + + // Вызов события: "разблокировка движения" + document.dispatchEvent( + new CustomEvent(`hotline.${_this.#id}.move.unblock`), + ); + } + }; + } + + if (this.wheel) { + // Запрошена возможность прокручивать колесом мыши + + // Инициализация обработчика наведения курсора (остановка движения) + this.#shell.onwheel = function (e) { + // Курсор наведён на бегущую + + // Инициализация буфера для временных данных + let buffer; + + // Перемещение + _this.offset( + (isNaN( + buffer = parseFloat( + _this.#shell.firstElementChild.style[ + _this.vertical ? "marginTop" : "marginLeft" + ], + ), + ) + ? 0 + : buffer) + + (_this.delta === null + ? e.wheelDelta + : e.wheelDelta > 0 + ? _this.delta + : -_this.delta), + ); + }; + } + + this.#status = "active"; + } + + if (this.observe) { + // Запрошено наблюдение за изменениями аттрибутов элемента бегущей строки + + if (this.#observer === null) { + // Отсутствует наблюдатель + + // Инициализация ссылки на ядро + const _this = this; + + // Инициализация наблюдателя + this.#observer = new MutationObserver(function (mutations) { + for (const mutation of mutations) { + if (mutation.type === "attributes") { + // Запись параметра в инстанцию бегущей строки + _this.configure(mutation.attributeName); + } + } + + // Перезапуск бегущей строки + _this.restart(); + }); + + // Активация наблюдения + this.#observer.observe(this.#shell, { + attributes: true, + }); + } + } else if (this.#observer instanceof MutationObserver) { + // Запрошено отключение наблюдения + + // Деактивация наблюдения + this.#observer.disconnect(); + + // Удаление наблюдателя + this.#observer = null; + } + + if (this.events.get("start")) { + // Запрошен вызов события: "запуск" + + // Вызов события: "запуск" + document.dispatchEvent( + new CustomEvent(`hotline.${this.#id}.start`), + ); + } + + return this; + } + + stop() { + this.#status = "inactive"; + + // Остановка бегущей строки + clearInterval(this.#instance); + + // Удаление инстанции интервала + this.#instance = null; + + if (this.events.get("stop")) { + // Запрошен вызов события: "остановка" + + // Вызов события: "остановка" + document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.stop`)); + } + + return this; + } + + restart() { + // Остановка бегущей строки + this.stop(); + + // Запуск бегущей строки + this.start(); + } + + configure(attribute) { + // Инициализация названия параметра + const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1]; + + if (typeof parameter === "string") { + // Параметр найден + + // Проверка на разрешение изменения + if (this.#block.has(parameter)) return; + + // Инициализация значения параметра + const value = this.#shell.getAttribute(attribute); + + if (typeof value !== undefined || typeof value !== null) { + // Найдено значение + + // Инициализация буфера для временных данных + let buffer; + + // Запись параметра + this[parameter] = isNaN(buffer = parseFloat(value)) + ? value === "true" ? true : value === "false" ? false : value + : buffer; + } + } + + return this; + } + + offset(value) { + // Запись отступа + this.#shell.firstElementChild.style[ + this.vertical ? "marginTop" : "marginLeft" + ] = value + "px"; + + if (this.events.get("offset")) { + // Запрошен вызов события: "сдвиг" + + // Вызов события: "сдвиг" + document.dispatchEvent( + new CustomEvent(`hotline.${this.#id}.offset`, { + detail: { + to: value, + }, + }), + ); + } + + return this; + } + + static preprocessing(event = false) { + // Инициализация счётчиков инстанций горячей строки + const success = new Set(); + let error = 0; + + for ( + const element of document.querySelectorAll('*[data-hotline="true"]') + ) { + // Перебор элементов для инициализации бегущих строк + + if (typeof element.id === "string") { + // Найден идентификатор + + // Инициализация инстанции бегущей строки + const hotline = new this(element.id, element); + + for (const attribute of element.getAttributeNames()) { + // Перебор аттрибутов + + // Запись параметра в инстанцию бегущей строки + hotline.configure(attribute); + } + + // Запуск бегущей строки + hotline.start(); + + // Запись инстанции бегущей строки в элемент + element.hotline = hotline; + + // Запись в счётчик успешных инициализаций + success.add(hotline); + } else ++error; + } + + if (event) { + // Запрошен вызов события: "предварительная подготовка" + + // Вызов события: "предварительная подготовка" + document.dispatchEvent( + new CustomEvent(`hotline.preprocessed`, { + detail: { + success, + error, + }, + }), + ); + } + } +}