mirror of
https://github.com/wassname/template.git
synced 2026-07-18 12:40:24 +08:00
192 lines
5.3 KiB
JavaScript
192 lines
5.3 KiB
JavaScript
// Copyright 2018 The Distill Template Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Figure
|
|
//
|
|
// d-figure provides a state-machine of visibility events:
|
|
//
|
|
// scroll out of view
|
|
// +----------------+
|
|
// *do work here* | |
|
|
// +----------------+ +-+---------+ +-v---------+
|
|
// | ready +----> onscreen | | offscreen |
|
|
// +----------------+ +---------^-+ +---------+-+
|
|
// | |
|
|
// +----------------+
|
|
// scroll into view
|
|
//
|
|
|
|
export class Figure extends HTMLElement {
|
|
|
|
static get is() { return 'd-figure'; }
|
|
|
|
static get readyQueue() {
|
|
if (!Figure._readyQueue) {
|
|
Figure._readyQueue = [];
|
|
}
|
|
return Figure._readyQueue;
|
|
}
|
|
|
|
static addToReadyQueue(figure) {
|
|
if (Figure.readyQueue.indexOf(figure) === -1) {
|
|
Figure.readyQueue.push(figure);
|
|
Figure.runReadyQueue();
|
|
}
|
|
}
|
|
|
|
static runReadyQueue() {
|
|
// console.log("Checking to run readyQueue, length: " + Figure.readyQueue.length + ", scrolling: " + Figure.isScrolling);
|
|
// if (Figure.isScrolling) return;
|
|
// console.log("Running ready Queue");
|
|
const figure = Figure.readyQueue
|
|
.sort((a,b) => a._seenOnScreen - b._seenOnScreen )
|
|
.filter((figure) => !figure._ready)
|
|
.pop();
|
|
if (figure) {
|
|
figure.ready();
|
|
requestAnimationFrame(Figure.runReadyQueue);
|
|
}
|
|
|
|
}
|
|
|
|
constructor() {
|
|
super();
|
|
// debugger
|
|
this._ready = false;
|
|
this._onscreen = false;
|
|
this._offscreen = true;
|
|
}
|
|
|
|
connectedCallback() {
|
|
this.loadsWhileScrolling = this.hasAttribute('loadsWhileScrolling');
|
|
Figure.marginObserver.observe(this);
|
|
Figure.directObserver.observe(this);
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
Figure.marginObserver.unobserve(this);
|
|
Figure.directObserver.unobserve(this);
|
|
}
|
|
|
|
// We use two separate observers:
|
|
// One with an extra 1000px margin to warn if the viewpoint gets close,
|
|
// And one for the actual on/off screen events
|
|
|
|
static get marginObserver() {
|
|
if (!Figure._marginObserver) {
|
|
// if (!('IntersectionObserver' in window)) {
|
|
// throw new Error('no interscetionobbserver!');
|
|
// }
|
|
const viewportHeight = window.innerHeight;
|
|
const margin = Math.floor(2 * viewportHeight);
|
|
const options = {rootMargin: margin + 'px 0px ' + margin + 'px 0px', threshold: 0.01};
|
|
const callback = Figure.didObserveMarginIntersection;
|
|
const observer = new IntersectionObserver(callback, options);
|
|
Figure._marginObserver = observer;
|
|
}
|
|
return Figure._marginObserver;
|
|
}
|
|
|
|
static didObserveMarginIntersection(entries) {
|
|
for (const entry of entries) {
|
|
const figure = entry.target;
|
|
if (entry.isIntersecting && !figure._ready) {
|
|
Figure.addToReadyQueue(figure);
|
|
}
|
|
}
|
|
}
|
|
|
|
static get directObserver() {
|
|
if (!Figure._directObserver) {
|
|
Figure._directObserver = new IntersectionObserver(
|
|
Figure.didObserveDirectIntersection, {
|
|
rootMargin: '0px', threshold: [0, 1.0],
|
|
}
|
|
);
|
|
}
|
|
return Figure._directObserver;
|
|
}
|
|
|
|
static didObserveDirectIntersection(entries) {
|
|
for (const entry of entries) {
|
|
const figure = entry.target;
|
|
if (entry.isIntersecting) {
|
|
figure._seenOnScreen = new Date();
|
|
// if (!figure._ready) { figure.ready(); }
|
|
if (figure._offscreen) { figure.onscreen(); }
|
|
} else {
|
|
if (figure._onscreen) { figure.offscreen(); }
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notify listeners that registered late, too:
|
|
|
|
addEventListener(eventName, callback) {
|
|
super.addEventListener(eventName, callback);
|
|
// if we had already dispatched something while presumingly no one was listening, we do so again
|
|
// debugger
|
|
if (eventName === 'ready') {
|
|
if (Figure.readyQueue.indexOf(this) !== -1) {
|
|
this._ready = false;
|
|
Figure.runReadyQueue();
|
|
}
|
|
}
|
|
if (eventName === 'onscreen') {
|
|
this.onscreen();
|
|
}
|
|
}
|
|
|
|
// Custom Events
|
|
|
|
ready() {
|
|
// debugger
|
|
this._ready = true;
|
|
Figure.marginObserver.unobserve(this);
|
|
const event = new CustomEvent('ready');
|
|
this.dispatchEvent(event);
|
|
}
|
|
|
|
onscreen() {
|
|
this._onscreen = true;
|
|
this._offscreen = false;
|
|
const event = new CustomEvent('onscreen');
|
|
this.dispatchEvent(event);
|
|
}
|
|
|
|
offscreen() {
|
|
this._onscreen = false;
|
|
this._offscreen = true;
|
|
const event = new CustomEvent('offscreen');
|
|
this.dispatchEvent(event);
|
|
}
|
|
|
|
}
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
Figure.isScrolling = false;
|
|
let timeout;
|
|
const resetTimer = () => {
|
|
Figure.isScrolling = true;
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => {
|
|
Figure.isScrolling = false;
|
|
Figure.runReadyQueue();
|
|
}, 500);
|
|
};
|
|
window.addEventListener('scroll', resetTimer, true);
|
|
|
|
}
|