PageSourceSearch

https://www.ylakainuu.fi/wp-content/themes/hilla-theme/assets/js/comments-scroll.js?version=2026090201

js ylakainuu.fi collected 2026-09-25 00:02:23 UTC 6,225 bytes, 182 lines download raw bytes

1/**
2 * Comments anchor scrolling (NAVIGA-667)
3 *
4 * The comment icons at the top and at the bottom of an article are plain
5 * anchors pointing at #coral_thread. The Coral thread, the paywall and the ads
6 * above them are loaded asynchronously, so the layout is still growing when the
7 * browser performs the native anchor jump - the page ends up scrolled short of
8 * the comments. This script takes over those jumps and keeps correcting the
9 * scroll position for a short while, until the page has settled.
10 */
11(function () {
12  var TARGET_ID = "coral_thread";
13  var ANCHOR_SELECTOR = 'a[href^="#' + TARGET_ID + '"]';
14  var SETTLE_TIME = 5000; // How long the position is corrected after a jump (ms)
15  var CHECK_INTERVAL = 100; // How often the position is re-checked (ms)
16  var TOLERANCE = 2; // Do not re-scroll for differences smaller than this (px)
17  var MARGIN = 8; // Breathing room between the sticky header and the target (px)
18
19  var correctionTimer = null;
20  var previousScrollBehavior = "";
21  var expectedPosition = -1;
22
23  /**
24   * Height of the sticky/fixed elements that cover the top of the viewport once
25   * the page is scrolled, so that the target does not end up underneath them.
26   * @returns {number} Offset in pixels
27   */
28  function getStickyOffset() {
29    var elements = document.querySelectorAll(".sticky-top, .fixed-top");
30    var offset = 0;
31
32    for (var i = 0; i < elements.length; i++) {
33      var style = window.getComputedStyle(elements[i]);
34      var position = style.position;
35      if (position !== "sticky" && position !== "-webkit-sticky" && position !== "fixed") {
36        continue;
37      }
38      if (style.display === "none" || style.visibility === "hidden") continue;
39
40      // Where the element ends up once it is pinned - it is not pinned yet when
41      // the user is still at the top of the page, so its current position in the
42      // viewport cannot be used.
43      var top = parseFloat(style.top);
44      if (isNaN(top) || top < 0) continue;
45
46      var height = elements[i].getBoundingClientRect().height;
47      if (height > 0) {
48        offset = Math.max(offset, top + height);
49      }
50    }
51
52    return offset;
53  }
54
55  /**
56   * Document position the target element should be scrolled to.
57   * @param {Element} target Element to scroll to
58   * @returns {number} Scroll position in pixels
59   */
60  function getTargetPosition(target) {
61    var top =
62      window.pageYOffset +
63      target.getBoundingClientRect().top -
64      getStickyOffset() -
65      MARGIN;
66
67    // Never ask for more than the page can actually scroll, otherwise the
68    // correction below would keep firing against a clamped position.
69    var maxScroll = Math.max(
70      0,
71      document.documentElement.scrollHeight - window.innerHeight
72    );
73
74    return Math.min(Math.max(0, top), maxScroll);
75  }
76
77  /**
78   * Stop correcting the scroll position and restore smooth scrolling.
79   */
80  function stopCorrecting() {
81    if (!correctionTimer) return;
82
83    clearInterval(correctionTimer);
84    correctionTimer = null;
85    expectedPosition = -1;
86    document.documentElement.style.scrollBehavior = previousScrollBehavior;
87  }
88
89  /**
90   * Scroll the window and remember where we put it, so that scrolling done by
91   * the user can be told apart from our own corrections.
92   * @param {number} position Scroll position in pixels
93   */
94  function scrollToPosition(position) {
95    window.scrollTo(0, position);
96    expectedPosition = window.pageYOffset;
97  }
98
99  /**
100   * Scroll to the target and keep it in place while the page is still growing.
101   * @param {Element} target Element to scroll to
102   */
103  function scrollToTarget(target) {
104    stopCorrecting();
105
106    // The site sets `html { scroll-behavior: smooth }`, which would animate every
107    // correction below and leave them restarting each other. Jump instantly.
108    previousScrollBehavior = document.documentElement.style.scrollBehavior;
109    document.documentElement.style.scrollBehavior = "auto";
110
111    var remaining = SETTLE_TIME;
112    correctionTimer = setInterval(function () {
113      remaining -= CHECK_INTERVAL;
114      if (remaining <= 0 || !document.body.contains(target)) {
115        stopCorrecting();
116        return;
117      }
118
119      var position = getTargetPosition(target);
120      if (Math.abs(window.pageYOffset - position) > TOLERANCE) {
121        scrollToPosition(position);
122      }
123    }, CHECK_INTERVAL);
124
125    scrollToPosition(getTargetPosition(target));
126  }
127
128  /**
129   * Any scrolling we did not ask for means the user has taken over - back off.
130   * A plain scroll listener is used on purpose: wheel and touch events that
131   * happen on top of the cross-origin Coral iframe never reach this document.
132   */
133  function onScroll() {
134    if (!correctionTimer) return;
135    if (Math.abs(window.pageYOffset - expectedPosition) > TOLERANCE) {
136      stopCorrecting();
137    }
138  }
139
140  /**
141   * Handle a click on a comment anchor.
142   * @param {Event} event Click event
143   */
144  function onClick(event) {
145    if (event.defaultPrevented) return;
146    if (event.button) return; // Not a plain left click
147    if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
148    if (!event.target || !event.target.closest) return;
149
150    var link = event.target.closest(ANCHOR_SELECTOR);
151    if (!link) return;
152
153    var target = document.getElementById(TARGET_ID);
154    if (!target) return; // Comments are not on the page - let the browser decide.
155
156    event.preventDefault();
157
158    // Add a history entry just like the native anchor would, so that Back takes
159    // the reader to where they were before the jump. Carry the existing state
160    // along - the SSO referer fix in custom-placeholder-top stores
161    // originalReferer there and reads it back later.
162    if (
163      window.history &&
164      window.history.pushState &&
165      window.location.hash !== "#" + TARGET_ID
166    ) {
167      window.history.pushState(
168        window.history.state,
169        document.title,
170        "#" + TARGET_ID
171      );
172    }
173
174    scrollToTarget(target);
175  }
176
177  // Nothing to do on pages without comment icons.
178  if (document.querySelector(ANCHOR_SELECTOR)) {
179    document.addEventListener("click", onClick);
180    window.addEventListener("scroll", onScroll, { passive: true });
181  }
182})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.