Remove snow fall
This commit is contained in:
parent
1dfa17abca
commit
328c9badb4
7 changed files with 0 additions and 465 deletions
|
@ -1,338 +0,0 @@
|
||||||
class Island extends HTMLElement {
|
|
||||||
static tagName = "is-land";
|
|
||||||
static prefix = "is-land--";
|
|
||||||
static attr = {
|
|
||||||
template: "data-island",
|
|
||||||
ready: "ready",
|
|
||||||
defer: "defer-hydration",
|
|
||||||
};
|
|
||||||
|
|
||||||
static onceCache = new Map();
|
|
||||||
static onReady = new Map();
|
|
||||||
|
|
||||||
static fallback = {
|
|
||||||
":not(is-land,:defined,[defer-hydration])": (readyPromise, node, prefix) => {
|
|
||||||
// remove from document to prevent web component init
|
|
||||||
let cloned = document.createElement(prefix + node.localName);
|
|
||||||
for(let attr of node.getAttributeNames()) {
|
|
||||||
cloned.setAttribute(attr, node.getAttribute(attr));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Declarative Shadow DOM (with polyfill)
|
|
||||||
let shadowroot = node.shadowRoot;
|
|
||||||
if(!shadowroot) {
|
|
||||||
let tmpl = node.querySelector(":scope > template:is([shadowrootmode], [shadowroot])");
|
|
||||||
if(tmpl) {
|
|
||||||
let mode = tmpl.getAttribute("shadowrootmode") || tmpl.getAttribute("shadowroot") || "closed";
|
|
||||||
shadowroot = node.attachShadow({ mode }); // default is closed
|
|
||||||
shadowroot.appendChild(tmpl.content.cloneNode(true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cheers to https://gist.github.com/developit/45c85e9be01e8c3f1a0ec073d600d01e
|
|
||||||
if(shadowroot) {
|
|
||||||
cloned.attachShadow({ mode: shadowroot.mode }).append(...shadowroot.childNodes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep *same* child nodes to preserve state of children (e.g. details->summary)
|
|
||||||
cloned.append(...node.childNodes);
|
|
||||||
node.replaceWith(cloned);
|
|
||||||
|
|
||||||
return readyPromise.then(() => {
|
|
||||||
// Restore original children and shadow DOM
|
|
||||||
if(cloned.shadowRoot) {
|
|
||||||
node.shadowRoot.append(...cloned.shadowRoot.childNodes);
|
|
||||||
}
|
|
||||||
node.append(...cloned.childNodes);
|
|
||||||
cloned.replaceWith(node);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// Internal promises
|
|
||||||
this.ready = new Promise(resolve => {
|
|
||||||
this.readyResolve = resolve;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// any parents of `el` that are <is-land> (with conditions)
|
|
||||||
static getParents(el, stopAt = false) {
|
|
||||||
let nodes = [];
|
|
||||||
while(el) {
|
|
||||||
if(el.matches && el.matches(Island.tagName)) {
|
|
||||||
if(stopAt && el === stopAt) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(Conditions.hasConditions(el)) {
|
|
||||||
nodes.push(el);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
el = el.parentNode;
|
|
||||||
}
|
|
||||||
return nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
static async ready(el, parents) {
|
|
||||||
if(!parents) {
|
|
||||||
parents = Island.getParents(el);
|
|
||||||
}
|
|
||||||
if(parents.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let imports = await Promise.all(parents.map(p => p.wait()));
|
|
||||||
// return innermost module import
|
|
||||||
if(imports.length) {
|
|
||||||
return imports[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forceFallback() {
|
|
||||||
if(window.Island) {
|
|
||||||
Object.assign(Island.fallback, window.Island.fallback);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let selector in Island.fallback) {
|
|
||||||
// Reverse here as a cheap way to get the deepest nodes first
|
|
||||||
let components = Array.from(this.querySelectorAll(selector)).reverse();
|
|
||||||
|
|
||||||
// with thanks to https://gist.github.com/cowboy/938767
|
|
||||||
for(let node of components) {
|
|
||||||
if(!node.isConnected) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let parents = Island.getParents(node);
|
|
||||||
// must be in a leaf island (not nested deep)
|
|
||||||
if(parents.length === 1) {
|
|
||||||
let p = Island.ready(node, parents);
|
|
||||||
Island.fallback[selector](p, node, Island.prefix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wait() {
|
|
||||||
return this.ready;
|
|
||||||
}
|
|
||||||
|
|
||||||
async connectedCallback() {
|
|
||||||
// Only use fallback content with loading conditions
|
|
||||||
if(Conditions.hasConditions(this)) {
|
|
||||||
// Keep fallback content without initializing the components
|
|
||||||
this.forceFallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.hydrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTemplates() {
|
|
||||||
return this.querySelectorAll(`template[${Island.attr.template}]`);
|
|
||||||
}
|
|
||||||
|
|
||||||
replaceTemplates(templates) {
|
|
||||||
// replace <template> with the live content
|
|
||||||
for(let node of templates) {
|
|
||||||
// if the template is nested inside another child <is-land> inside, skip
|
|
||||||
if(Island.getParents(node, this).length > 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = node.getAttribute(Island.attr.template);
|
|
||||||
// get rid of the rest of the content on the island
|
|
||||||
if(value === "replace") {
|
|
||||||
let children = Array.from(this.childNodes);
|
|
||||||
for(let child of children) {
|
|
||||||
this.removeChild(child);
|
|
||||||
}
|
|
||||||
this.appendChild(node.content);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
let html = node.innerHTML;
|
|
||||||
if(value === "once" && html) {
|
|
||||||
if(Island.onceCache.has(html)) {
|
|
||||||
node.remove();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Island.onceCache.set(html, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
node.replaceWith(node.content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async hydrate() {
|
|
||||||
let conditions = [];
|
|
||||||
if(this.parentNode) {
|
|
||||||
// wait for all parents before hydrating
|
|
||||||
conditions.push(Island.ready(this.parentNode));
|
|
||||||
}
|
|
||||||
|
|
||||||
let attrs = Conditions.getConditions(this);
|
|
||||||
for(let condition in attrs) {
|
|
||||||
if(Conditions.map[condition]) {
|
|
||||||
conditions.push(Conditions.map[condition](attrs[condition], this));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loading conditions must finish before dependencies are loaded
|
|
||||||
await Promise.all(conditions);
|
|
||||||
|
|
||||||
this.replaceTemplates(this.getTemplates());
|
|
||||||
|
|
||||||
for(let fn of Island.onReady.values()) {
|
|
||||||
await fn.call(this, Island);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.readyResolve();
|
|
||||||
|
|
||||||
this.setAttribute(Island.attr.ready, "");
|
|
||||||
|
|
||||||
// Remove [defer-hydration]
|
|
||||||
this.querySelectorAll(`[${Island.attr.defer}]`).forEach(node => node.removeAttribute(Island.attr.defer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Conditions {
|
|
||||||
static map = {
|
|
||||||
visible: Conditions.visible,
|
|
||||||
idle: Conditions.idle,
|
|
||||||
interaction: Conditions.interaction,
|
|
||||||
media: Conditions.media,
|
|
||||||
"save-data": Conditions.saveData,
|
|
||||||
};
|
|
||||||
|
|
||||||
static hasConditions(node) {
|
|
||||||
return Object.keys(Conditions.getConditions(node)).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static getConditions(node) {
|
|
||||||
let map = {};
|
|
||||||
for(let key of Object.keys(Conditions.map)) {
|
|
||||||
if(node.hasAttribute(`on:${key}`)) {
|
|
||||||
map[key] = node.getAttribute(`on:${key}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
static visible(noop, el) {
|
|
||||||
if(!('IntersectionObserver' in window)) {
|
|
||||||
// runs immediately
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
let observer = new IntersectionObserver(entries => {
|
|
||||||
let [entry] = entries;
|
|
||||||
if(entry.isIntersecting) {
|
|
||||||
observer.unobserve(entry.target);
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
observer.observe(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warning: on:idle is not very useful with other conditions as it may resolve long before.
|
|
||||||
static idle() {
|
|
||||||
let onload = new Promise(resolve => {
|
|
||||||
if(document.readyState !== "complete") {
|
|
||||||
window.addEventListener("load", () => resolve(), { once: true });
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!("requestIdleCallback" in window)) {
|
|
||||||
// run immediately
|
|
||||||
return onload;
|
|
||||||
}
|
|
||||||
|
|
||||||
// both idle and onload
|
|
||||||
return Promise.all([
|
|
||||||
new Promise(resolve => {
|
|
||||||
requestIdleCallback(() => {
|
|
||||||
resolve();
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
onload,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static interaction(eventOverrides, el) {
|
|
||||||
let events = ["click", "touchstart"];
|
|
||||||
// event overrides e.g. on:interaction="mouseenter"
|
|
||||||
if(eventOverrides) {
|
|
||||||
events = (eventOverrides || "").split(",").map(entry => entry.trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
function resolveFn(e) {
|
|
||||||
resolve();
|
|
||||||
|
|
||||||
// cleanup the other event handlers
|
|
||||||
for(let name of events) {
|
|
||||||
el.removeEventListener(name, resolveFn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let name of events) {
|
|
||||||
el.addEventListener(name, resolveFn, { once: true });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static media(query) {
|
|
||||||
let mm = {
|
|
||||||
matches: true
|
|
||||||
};
|
|
||||||
|
|
||||||
if(query && ("matchMedia" in window)) {
|
|
||||||
mm = window.matchMedia(query);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(mm.matches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
|
||||||
mm.addListener(e => {
|
|
||||||
if(e.matches) {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static saveData(expects) {
|
|
||||||
// return early if API does not exist
|
|
||||||
if(!("connection" in navigator) || navigator.connection.saveData === (expects !== "false")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// dangly promise
|
|
||||||
return new Promise(() => {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should this auto define? Folks can redefine later using { component } export
|
|
||||||
if("customElements" in window) {
|
|
||||||
window.customElements.define(Island.tagName, Island);
|
|
||||||
window.Island = Island;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Island,
|
|
||||||
Island as component, // Backwards compat only: recommend `Island` export
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO remove in 4.0
|
|
||||||
export const ready = Island.ready; // Backwards compat only: recommend `Island` export
|
|
Binary file not shown.
Binary file not shown.
|
@ -1,117 +0,0 @@
|
||||||
class Snow extends HTMLElement {
|
|
||||||
static random(min, max) {
|
|
||||||
return min + Math.floor(Math.random() * (max - min) + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
static attrs = {
|
|
||||||
count: "count", // default: 100
|
|
||||||
mode: "mode",
|
|
||||||
}
|
|
||||||
|
|
||||||
generateCss(mode, count) {
|
|
||||||
let css = [];
|
|
||||||
css.push(`
|
|
||||||
:host([mode="element"]) {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
:host([mode="page"]) {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
:host([mode="page"]),
|
|
||||||
:host([mode="element"]) > * {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
:host([mode="element"]) ::slotted(*) {
|
|
||||||
pointer-events: all;
|
|
||||||
}
|
|
||||||
* {
|
|
||||||
position: absolute;
|
|
||||||
width: var(--snow-fall-size, 10px);
|
|
||||||
height: var(--snow-fall-size, 10px);
|
|
||||||
background: var(--snow-fall-color, rgba(255,255,255,.5));
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
|
|
||||||
// using vw units (max 100)
|
|
||||||
let dimensions = { width: 100, height: 100 };
|
|
||||||
let units = { x: "vw", y: "vh"};
|
|
||||||
|
|
||||||
if(mode === "element") {
|
|
||||||
dimensions = {
|
|
||||||
width: this.firstElementChild.clientWidth,
|
|
||||||
height: this.firstElementChild.clientHeight,
|
|
||||||
};
|
|
||||||
units = { x: "px", y: "px"};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thank you @alphardex: https://codepen.io/alphardex/pen/dyPorwJ
|
|
||||||
for(let j = 1; j<= count; j++ ) {
|
|
||||||
let x = Snow.random(1, 100) * dimensions.width/100; // vw
|
|
||||||
let offset = Snow.random(-10, 10) * dimensions.width/100; // vw
|
|
||||||
|
|
||||||
let yoyo = Math.round(Snow.random(30, 100)); // % time
|
|
||||||
let yStart = yoyo * dimensions.height/100; // vh
|
|
||||||
let yEnd = dimensions.height; // vh
|
|
||||||
|
|
||||||
let scale = Snow.random(1, 10000) * .0001;
|
|
||||||
let duration = Snow.random(10, 30);
|
|
||||||
let delay = Snow.random(0, 30) * -1;
|
|
||||||
|
|
||||||
css.push(`
|
|
||||||
:nth-child(${j}) {
|
|
||||||
opacity: ${Snow.random(0, 1000) * 0.001};
|
|
||||||
transform: translate(${x}${units.x}, -10px) scale(${scale});
|
|
||||||
animation: fall-${j} ${duration}s ${delay}s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fall-${j} {
|
|
||||||
${yoyo}% {
|
|
||||||
transform: translate(${x + offset}${units.x}, ${yStart}${units.y}) scale(${scale});
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: translate(${x + offset / 2}${units.x}, ${yEnd}${units.y}) scale(${scale});
|
|
||||||
}
|
|
||||||
}`)
|
|
||||||
}
|
|
||||||
return css.join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
connectedCallback() {
|
|
||||||
// https://caniuse.com/mdn-api_cssstylesheet_replacesync
|
|
||||||
if(this.shadowRoot || !("replaceSync" in CSSStyleSheet.prototype)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let count = parseInt(this.getAttribute(Snow.attrs.count)) || 100;
|
|
||||||
|
|
||||||
let mode;
|
|
||||||
if(this.hasAttribute(Snow.attrs.mode)) {
|
|
||||||
mode = this.getAttribute(Snow.attrs.mode);
|
|
||||||
} else {
|
|
||||||
mode = this.firstElementChild ? "element" : "page";
|
|
||||||
this.setAttribute(Snow.attrs.mode, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sheet = new CSSStyleSheet();
|
|
||||||
sheet.replaceSync(this.generateCss(mode, count));
|
|
||||||
|
|
||||||
let shadowroot = this.attachShadow({ mode: "open" });
|
|
||||||
shadowroot.adoptedStyleSheets = [sheet];
|
|
||||||
|
|
||||||
let d = document.createElement("div");
|
|
||||||
for(let j = 0, k = count; j<k; j++) {
|
|
||||||
shadowroot.appendChild(d.cloneNode());
|
|
||||||
}
|
|
||||||
|
|
||||||
shadowroot.appendChild(document.createElement("slot"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
customElements.define("snow-fall", Snow);
|
|
Binary file not shown.
Binary file not shown.
|
@ -72,16 +72,6 @@
|
||||||
<!--scripts go here when needed-->
|
<!--scripts go here when needed-->
|
||||||
@section('scripts')
|
@section('scripts')
|
||||||
<script type="module" src="/assets/js/app.js"></script>
|
<script type="module" src="/assets/js/app.js"></script>
|
||||||
|
|
||||||
<!-- Snow fall -->
|
|
||||||
<script type="module" src="/assets/frontend/is-land.js"></script>
|
|
||||||
<script type="module" src="/assets/frontend/snow-fall.js"></script>
|
|
||||||
<is-land on:media="(prefers-reduced-motion: no-preference)">
|
|
||||||
<snow-fall
|
|
||||||
count="240"
|
|
||||||
style="--snow-fall-color: rebeccapurple; --snow-fall-size: 8px"
|
|
||||||
></snow-fall>
|
|
||||||
</is-land>
|
|
||||||
@show
|
@show
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Add table
Reference in a new issue