Skip to content

Commit 42af77e

Browse files
committed
feat(pat inject): Rework autoload-visible to use an IntersectionObserver.
The autoload-visible trigger of pat-inject now uses an IntersectionObserver. This simplifies the code and improves performance because there are no more complex position calculations involved.
1 parent 4fe83f1 commit 42af77e

File tree

2 files changed

+47
-86
lines changed

2 files changed

+47
-86
lines changed

src/pat/inject/index.html

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<!DOCTYPE html>
22
<html>
33
<head>
4-
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
4+
<meta charset="utf-8"/>
55
<!--meta name="patterns-push-url" content="ws://push.quaivecloud.com/ws" /-->
6-
<meta name="patterns-push-url" content="ws://localhost:15674/ws" />
7-
<meta name="patterns-push-exchange" content="patternslib" />
8-
<meta name="patterns-push-login" content="guest" />
9-
<meta name="patterns-push-password" content="guest" />
10-
<meta name="patterns-push-filter" content="allan_neece" />
11-
<meta name="pat-inject-status-404" content="./test_404.html" />
6+
<!--meta name="patterns-push-url" content="ws://localhost:15674/ws" /-->
7+
<!--meta name="patterns-push-exchange" content="patternslib" /-->
8+
<!--meta name="patterns-push-login" content="guest" /-->
9+
<!--meta name="patterns-push-password" content="guest" /-->
10+
<!--meta name="patterns-push-filter" content="allan_neece" /-->
11+
<!--meta name="pat-inject-status-404" content="./test_404.html" /-->
1212
<title>Injection pattern</title>
1313
<link rel="stylesheet" href="/style/common.css" type="text/css" />
1414
<script
@@ -19,6 +19,10 @@
1919
</head>
2020

2121
<body>
22+
<section>
23+
<a id="top" class="pat-scroll" href="#bottom">scroll to bottom</a>
24+
</section>
25+
2226
<article class="pat-rich">
2327
<p>
2428
In this example, a
@@ -526,6 +530,10 @@ <h3>
526530
>
527531
</section>
528532

533+
<section>
534+
<a id="bottom" class="pat-scroll" href="#top">scroll to top</a>
535+
</section>
536+
529537
<style>
530538
body {
531539
font-size: 16px;

src/pat/inject/inject.js

Lines changed: 32 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import registry from "../../core/registry";
1010
import utils from "../../core/utils";
1111

1212
const log = logging.getLogger("pat.inject");
13+
//log.setLevel(logging.Level.DEBUG);
14+
1315
const TEXT_NODE = 3;
1416
const COMMENT_NODE = 8;
1517

@@ -136,7 +138,7 @@ const inject = {
136138
}
137139
break;
138140
case "autoload-visible":
139-
this._initAutoloadVisible($el, cfgs);
141+
this._initAutoloadVisible($el);
140142
break;
141143
case "idle":
142144
this._initIdleTrigger($el, cfgs[0].delay);
@@ -960,102 +962,53 @@ const inject = {
960962
return $html;
961963
},
962964

963-
// XXX: hack
964-
_initAutoloadVisible($el, cfgs) {
965+
_initAutoloadVisible($el) {
965966
if ($el.data("pat-inject-autoloaded")) {
966967
// ignore executed autoloads
967968
return false;
968969
}
969-
const $scrollable = $el.parents(":scrollable");
970+
971+
const el = $el[0];
970972

971973
// function to trigger the autoload and mark as triggered
972974
const trigger = (event) => {
973975
if ($el.data("pat-inject-autoloaded")) {
976+
log.debug(`autoload-visible trigger skipped ${el}`);
974977
return false;
975978
}
976979
$el.data("pat-inject-autoloaded", true);
977-
this.onTrigger({ currentTarget: $el[0] });
980+
this.onTrigger({ currentTarget: el });
978981
event && event.preventDefault();
982+
log.debug(`autoload-visible trigger run ${el}`);
979983
return true;
980984
};
981-
$el.click(trigger);
982985

983-
// Use case 1: a (heigh-constrained) scrollable parent
984-
if ($scrollable.length) {
985-
// if scrollable parent and visible -> trigger it
986-
// we only look at the closest scrollable parent, no nesting
987-
// Check visibility for scrollable
988-
const checkVisibility = utils.debounce(() => {
989-
if ($el.data("patterns.autoload") || !$.contains(document, $el[0])) {
990-
return false;
991-
}
992-
if (!$el.is(":visible")) {
993-
return false;
994-
}
995-
if (!utils.elementInViewport($el[0])) {
996-
return false;
997-
}
998-
// check if the target element still exists. Otherwise halt and catch fire
999-
const target = (
1000-
$el.data("pat-inject")[0].target || cfgs[0].defaultSelector
1001-
).replace(/::element/, "");
1002-
if (target && target !== "self" && $(target).length === 0) {
1003-
return false;
1004-
}
986+
// Config see: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
987+
const intersection_observer_config = {
988+
threshold: 0, // If even one pixel is visible, the callback will be run.
989+
root: null, // Root is browser viewport. If the element is visible to the user, the callback will be run.
990+
margin: "0px", // No margins. The element is not preloaded.
991+
};
1005992

1006-
// checkVisibility was possibly installed as a scroll
1007-
// handler and has now served its purpose -> remove
1008-
$($scrollable[0]).off("scroll", checkVisibility);
1009-
$(window).off("resize.pat-autoload", checkVisibility);
1010-
return trigger();
1011-
}, 100);
1012-
if (checkVisibility()) {
1013-
return true;
1014-
}
1015-
// wait to become visible - again only immediate scrollable parent
1016-
$($scrollable[0]).on("scroll", checkVisibility);
1017-
$(window).on("resize.pat-autoload", checkVisibility);
1018-
} else {
1019-
// Use case 2: scrolling the entire page
1020-
// Check visibility for non-scrollable
1021-
const checkVisibility = utils.debounce(() => {
1022-
if ($el.parents(":scrollable").length) {
1023-
// Because of a resize the element has now a scrollable parent
1024-
// and we should reset the correct event
1025-
$(window).off(".pat-autoload", checkVisibility);
1026-
return this._initAutoloadVisible($el);
1027-
}
1028-
if ($el.data("patterns.autoload")) {
1029-
return false;
1030-
}
1031-
if (!$el.is(":visible")) {
1032-
return false;
1033-
}
1034-
if (!utils.elementInViewport($el[0])) {
1035-
return false;
1036-
}
1037-
// check if the target element still exists. Otherwise halt and catch fire
1038-
const target = (
1039-
$el.data("pat-inject")[0].target || cfgs[0].defaultSelector
1040-
).replace(/::element/, "");
1041-
if (target && target !== "self" && $(target).length === 0) {
1042-
return false;
993+
let timeout_id = null;
994+
const observer = new IntersectionObserver((entries) => {
995+
for (const entry of entries) {
996+
if (entry.isIntersecting) {
997+
// Run the callback after 200ms to prevent loading all
998+
// visible elements when scrolling over.
999+
timeout_id = window.setTimeout(() => {
1000+
observer.unobserve(entry.target); // Stop observing loaded elements.
1001+
trigger();
1002+
}, 200);
1003+
log.debug(`autoload-visible intersecting ${el}`);
1004+
} else {
1005+
window.clearTimeout(timeout_id);
1006+
log.debug(`autoload-visible not intersecting ${el}`);
10431007
}
1044-
$(window).off(".pat-autoload", checkVisibility);
1045-
return trigger();
1046-
}, 100);
1047-
if (checkVisibility()) {
1048-
return true;
10491008
}
1050-
// https://github.com/w3c/IntersectionObserver/tree/master/polyfill
1051-
if (IntersectionObserver) {
1052-
const observer = new IntersectionObserver(checkVisibility);
1053-
$el.each((idx, el) => observer.observe(el));
1054-
} else {
1055-
$(window).on("resize.pat-autoload scroll.pat-autoload", checkVisibility);
1056-
}
1057-
}
1058-
return false;
1009+
}, intersection_observer_config);
1010+
observer.observe(el);
1011+
$el.click(trigger);
10591012
},
10601013

10611014
_initIdleTrigger($el, delay) {

0 commit comments

Comments
 (0)