117 lines
2.8 KiB
JavaScript
117 lines
2.8 KiB
JavaScript
|
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);
|