diff --git a/package.json b/package.json index e0d9001..d0112a1 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "@webcomponents/webcomponentsjs": "^1.0.7", "assert": "^1.4.1", "d3-time-format": "^2.0.3", + "intersection-observer": "^0.4.0", "jsdom-wc": "^11.0.0-alpha-1", "katex": "^0.7.1" } diff --git a/rollup.config.js b/rollup.config.js index b7f4371..2fc121b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -37,6 +37,7 @@ componentsConfig.plugins.push(copy({ './node_modules/katex/dist/fonts': 'dist/fonts', './node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js': 'dist/webcomponents-lite.js', './node_modules/@webcomponents/webcomponentsjs/webcomponents-lite.js.map': 'dist/webcomponents-lite.js.map', + './node_modules/intersection-observer/intersection-observer.js': 'dist/intersection-observer.js', })); Object.assign(transformsConfig, defaultConfig); diff --git a/src/components/d-figure.js b/src/components/d-figure.js index be5b90b..a5bc76a 100644 --- a/src/components/d-figure.js +++ b/src/components/d-figure.js @@ -1,28 +1,17 @@ -`Figure - -d-figure provides a state-machine of visibility events: - - scroll out of view - +----------------+ - *do work here* | | -+------+ +----------------+ +-+---------+ +-v---------+ -| init +----> almostonscreen +----> onscreen | | offscreen | -+------+ +----------------+ +---------^-+ +---------+-+ - | | - +----------------+ - scroll into view - -TODO: -* default styling -* get rid of init state? -* shadow dom? -* resizing/default size? -* offer centerscreen event? -* polyfills - -` - -const intersectionMargin = 1000; +// 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 { @@ -30,81 +19,88 @@ export class Figure extends HTMLElement { constructor() { super(); - - // keep track of communicated state - this._init = false; + this._ready = false; this._onscreen = false; this._offscreen = true; - - // 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 - this._marginObserver = new IntersectionObserver((entries) => { - for (const entry of entries) { - if (entry.isIntersecting && !this._almostonscreen) { - this.almostonscreen(); - this._marginObserver.disconnect(); - } - } - }, { - root: null, // listen on entire viewport - rootMargin: intersectionMargin + 'px 0px ' + intersectionMargin + 'px 0px', - threshold: 1.0, - }); - this._directObserver = new IntersectionObserver((entries) => { - for (const entry of entries) { - if (entry.isIntersecting) { - if (!this._almostonscreen) { this.almostonscreen(); } - if (this._offscreen) { this.onscreen(); } - } else { - if (this._onscreen) { this.offscreen(); } - } - } - }, { - root: null, // listen on entire viewport - rootMargin: '0px', - threshold: [0, 1.0], - }); - } connectedCallback() { - this._marginObserver.observe(this); - this._directObserver.observe(this); - this.init(); + Figure.marginObserver.observe(this); + Figure.directObserver.observe(this); } + disconnectedCallback() { + Figure.marginObserver.unobserve(this); + Figure.directObserver.unobserve(this); + } - /* Override addEventListener - * We want to notify elements that register late as well. - */ + // 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) { + const viewportHeight = window.innerHeight; + const margin = Math.floor(2 * viewportHeight); + Figure._marginObserver = new IntersectionObserver( + Figure.didObserveMarginIntersection, { + rootMargin: margin + 'px 0px ' + margin + 'px 0px', threshold: 0.01, + }); + } + return Figure._marginObserver; + } + + static didObserveMarginIntersection(entries) { + for (const entry of entries) { + const figure = entry.target; + if (entry.isIntersecting && !figure._ready) { + figure.ready(); + } + } + } + + 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) { + 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 - if (this._init && eventName === 'init') { - this.init(); - } - if (this._almostonscreen && eventName === 'almostonscreen') { - this.almostonscreen(); + if (this._ready && eventName === 'ready') { + this.ready(); } if (this._onscreen && eventName === 'onscreen') { this.onscreen(); } } + // Custom Events - /* Custom Events */ - - init() { - this._init = true; - const event = new CustomEvent('init'); - this.dispatchEvent(event); - } - - almostonscreen() { - this._almostonscreen = true; - const event = new CustomEvent('almostonscreen'); + ready() { + this._ready = true; + this.marginObserver.unobserve(this); + const event = new CustomEvent('ready'); this.dispatchEvent(event); } diff --git a/src/styles/styles-layout.css b/src/styles/styles-layout.css index 65c3908..d8a863f 100644 --- a/src/styles/styles-layout.css +++ b/src/styles/styles-layout.css @@ -52,7 +52,8 @@ d-article > * { grid-column: margin-left / body; } -.l-page { +.l-page, +d-figure { grid-column: margin-left / page; } diff --git a/src/transforms/polyfills.js b/src/transforms/polyfills.js index 419124a..d479c15 100644 --- a/src/transforms/polyfills.js +++ b/src/transforms/polyfills.js @@ -1,17 +1,38 @@ const webcomponentPath = '../dist/webcomponents-lite.js'; +const intersectionObserverPath = '../dist/intersection-observer.js'; const template = ` - if ('registerElement' in document && - 'import' in document.createElement('link') && - 'content' in document.createElement('template')) { - // Platform supports webcomponents natively! :-) - } else { - // Platform does not support webcomponents--loading polyfills synchronously. - const scriptTag = document.createElement('script'); - scriptTag.src = '${webcomponentPath}'; - scriptTag.async = false; - document.currentScript.parentNode.insertBefore(scriptTag, document.currentScript.nextSibling); - } +if ('IntersectionObserver' in window && + 'IntersectionObserverEntry' in window && + 'intersectionRatio' in IntersectionObserverEntry.prototype) { + // Platform supports IntersectionObserver natively! :-) + if (!('isIntersecting' in IntersectionObserverEntry.prototype)) { + Object.defineProperty(IntersectionObserverEntry.prototype, + 'isIntersecting', { + get: function () { + return this.intersectionRatio > 0; + } + }); + } +} else { + // Platform does not support webcomponents--loading polyfills synchronously. + const scriptTag = document.createElement('script'); + scriptTag.src = '${intersectionObserverPath}'; + scriptTag.async = false; + document.currentScript.parentNode.insertBefore(scriptTag, document.currentScript.nextSibling); +} + +if ('registerElement' in document && + 'import' in document.createElement('link') && + 'content' in document.createElement('template')) { + // Platform supports webcomponents natively! :-) +} else { + // Platform does not support webcomponents--loading polyfills synchronously. + const scriptTag = document.createElement('script'); + scriptTag.src = '${webcomponentPath}'; + scriptTag.async = false; + document.currentScript.parentNode.insertBefore(scriptTag, document.currentScript.nextSibling); +} `; export default function render(dom) {