Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name ejchan
- // @namespace ejchan-popup-anchor
- // @version 1.0
- // @description Всплывающий пост (.pv) появляется под ссылкой, из которой открыт, и прокручивается вместе со страницей
- // @match *://*/*
- // @run-at document-idle
- // @grant none
- // ==/UserScript==
- (function () {
- 'use strict';
- // ---- настройки ----
- const CENTER_CAP = 40; // на сколько px правее левого края ссылки начинается пост (максимум; обычно середина ссылки)
- const OFFSET_Y = 0; // зазор между нижним краем ссылки и верхом поста
- const MARGIN = 8; // минимальный отступ от краёв окна
- const SELECTOR_LINK = 'a, .post__ref';
- let lastLink = null; // последняя ссылка, на которую наводили курсор
- const anchors = new WeakMap(); // всплывающий пост -> ссылка-источник
- document.addEventListener('mouseover', (e) => {
- const t = e.target;
- const a = t && t.closest ? t.closest(SELECTOR_LINK) : null;
- if (a) lastLink = a;
- }, true);
- // Где в координатах окна находится точка (0,0) для absolute-позиционирования этого элемента
- function origin(pv) {
- const op = pv.offsetParent;
- if (
- op &&
- op !== document.documentElement &&
- (op !== document.body || getComputedStyle(op).position !== 'static')
- ) {
- const pr = op.getBoundingClientRect();
- return {
- x: pr.left + op.clientLeft - op.scrollLeft,
- y: pr.top + op.clientTop - op.scrollTop,
- };
- }
- return { x: -window.scrollX, y: -window.scrollY };
- }
- function place(pv) {
- const a = anchors.get(pv);
- if (!a || !a.isConnected) return;
- const r = a.getBoundingClientRect();
- const s = pv.style;
- // absolute вместо fixed: пост прокручивается вместе со страницей
- s.setProperty('position', 'absolute', 'important');
- s.setProperty('right', 'auto', 'important');
- s.setProperty('bottom', 'auto', 'important');
- s.setProperty('margin', '0', 'important');
- s.setProperty('transform', 'none', 'important');
- const w = pv.offsetWidth;
- const vw = document.documentElement.clientWidth;
- let x = r.left + Math.min(r.width / 2, CENTER_CAP);
- if (w) x = Math.min(x, vw - w - MARGIN); // не вылезать за правый край окна
- x = Math.max(MARGIN, x);
- const y = r.bottom + OFFSET_Y;
- const o = origin(pv);
- s.setProperty('left', (x - o.x) + 'px', 'important');
- s.setProperty('top', (y - o.y) + 'px', 'important');
- }
- const mo = new MutationObserver((records) => {
- const todo = new Set();
- for (const m of records) {
- if (m.type === 'childList') {
- m.addedNodes.forEach((n) => {
- if (n.nodeType !== 1) return;
- const list = [];
- if (n.matches('.pv')) list.push(n);
- n.querySelectorAll('.pv').forEach((el) => list.push(el));
- list.forEach((pv) => {
- anchors.set(pv, lastLink); // новый пост — запоминаем ссылку-источник
- todo.add(pv);
- });
- });
- } else if (m.type === 'attributes') {
- const t = m.target;
- if (t.classList && t.classList.contains('pv')) {
- if (!anchors.has(t)) anchors.set(t, lastLink);
- todo.add(t); // сайт перекинул пост по своим координатам — возвращаем на место
- }
- }
- }
- todo.forEach(place);
- mo.takeRecords(); // выкидываем записи о наших собственных изменениях, чтобы не зациклиться
- });
- mo.observe(document.documentElement, {
- childList: true,
- subtree: true,
- attributes: true,
- attributeFilter: ['style', 'class'],
- });
- })();
Advertisement
Add Comment
Please, Sign In to add comment