Repository: mantoufan/yzhanWeather Branch: master Commit: 9bf902801dad Files: 10 Total size: 22.2 KB Directory structure: gitextract_im5qcwqy/ ├── .gitignore ├── .swcrc ├── LICENSE ├── README.md ├── docs/ │ └── index.html ├── old/ │ └── mtfWeather.js ├── package.json ├── src/ │ ├── conf.js │ └── yzhanweather.js └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules ================================================ FILE: .swcrc ================================================ { "$schema": "https://json.schemastore.org/swcrc", "minify": false } ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2023 馒头饭 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # yzhanWeather ![npm](https://img.shields.io/npm/v/yzhanweather) ![npm bundle size](https://img.shields.io/bundlephobia/minzip/yzhanweather) ![npm](https://img.shields.io/npm/dt/yzhanweather) [![GitHub license](https://img.shields.io/github/license/mantoufan/yzhanweather)](https://github.com/mantoufan/yzhanweather/blob/main/LICENSE) ![ie11](https://img.shields.io/badge/IE-11-skyblue) Pure CSS animation for sakura, rain, snow, firefly and butterfly effects, high performance without affecting SEO 纯 CSS 动画实现樱花、雨、雪、萤火虫和蝴蝶飞舞背景效果,高性能且不影响 SEO **1kB Series Lib** Fully functional with gzip code 1kB and keep source code readable ## Quick Start ### Setup #### Node.js ```javascript npm i yzhanweather import YZhanWeather from 'yzhanweather' ``` #### Browser ```html ``` ### Usage ```javascript const yzhanweather = new YZhanWeather() yzhanweather.run('firefly') // Options: sakura | snow | firefly | rain | butterfly yzhanweather.run('firefly', { maxDuration: 10 // Default: 10s, this option can determine the speed of animations }) yzhanweather.clear() // Stop and clear all animations yzhanweather.destory() // Destory the instance and free up memory ``` ## Demo [Online Demo](https://mantoufan.github.io/yzhanWeather/) *Note: click the select on the left-top to change weather* ### GIF sakura ![](https://s2.loli.net/2023/02/26/FXZwUh5pA3P6xHT.gif) snow ![](https://s2.loli.net/2023/02/26/TKJy1qS9LHgntFC.gif) firefly ![](https://s2.loli.net/2023/02/26/DnhjpbHgPizZrw2.gif) rain ![](https://s2.loli.net/2023/02/26/X2RjoHuw18SslxD.gif) butterfly ![](https://s2.loli.net/2023/02/26/QlyUdq3jeRThkNZ.gif) ## Performance comparsion We use a page from madfan including a 720P video background, collect data when rendering 40 butterflies at the same time. - With Old Version *Note: By GIF and JavaScript in **old** folder* ![the result of old version](https://s2.loli.net/2023/02/26/tb4m3GvEHdhxCq2.jpg) - With Pure CSS Version ![the result of new version](https://s2.loli.net/2023/02/26/v9dJaItjX1Z6Unh.jpg) No long tasks, almost negligible CPU and GPU usage ## Config All config including speed, num and css tpl are in `conf.js` under the *src* folder. You could change it and then `npm run build` your own version. You can set `maxDuration` to control the speed of animations without changing the `conf.js` ================================================ FILE: docs/index.html ================================================ YZhan Weather Demo
================================================ FILE: old/mtfWeather.js ================================================ (function () { var d = 16, os = false; function of() { var d = document.createElement("div"); return "undefined" !== typeof d.style["opacity"]; } function g(a) { a(); } function h() { var a = Object.create(null); for (type in { Top: "", Left: "", }) { var b = type == "Top" ? "Y" : "X"; if (typeof window["page" + b + "Offset"] !== "undefined") { a[type.toLowerCase()] = window["page" + b + "Offset"]; } else { b = document.documentElement.clientHeight ? document.documentElement : document.body; a[type.toLowerCase()] = b["scroll" + type]; } } return a; } function l(s) { var a = document.body, b; if (window.innerHeight) { b = window.innerHeight; } else if (a.parentElement.clientHeight) { b = a.parentElement.clientHeight; } else if (a && a.clientHeight) { b = a.clientHeight; } return b - (s || 0) - d; // 安全距离,避免滚动条闪现 } function w(s) { return document.documentElement.clientWidth - (s || 0) - d; //安全距离,避免滚动条闪现 } function i(a) { this.create(a); } var j = false; g(function () { j = true; }); var f = true, m, c = []; window.mtfWeather = function (a, b) { if (a === "play") { f = true; } else if (a === "stop") { f = false; } else { if (j) { os = of(); if (m) { clearInterval(m); } m = setInterval(function () { f && c.length < b && Math.random() < 0.1 && c.push(new i(a)); for (var e = h().top, d = c.length - 1; d >= 0; d--) if (c[d]) if ( c[d].top < e || c[d].top > e + c[d].maxTop || (os && c[d].el.style.opacity < 0.01) ) { c[d].remove(); c[d] = null; c.splice(d, 1); } else { c[d].move(); c[d].draw(); } }, 60); } else g(function () { mtfWeather(a, b); }); } }; i.prototype = { common: function (o) { o.style.cssText = "position:absolute;display:block;z-index:99999;width:" + o.size + "px;height:" + o.size + "px"; }, create: function (a) { switch (a) { case "雪": this.el = cache.get( "./mtf/mtfWeatherPic/snow" + Math.floor(Math.random() * 5) + ".gif" ); this.el.size = (Math.random() * 40 + 10) | 0; this.common(this.el); this.maxLeft = w(this.el.size); this.maxTop = l(this.el.size); this.left = Math.random() * this.maxLeft; this.top = h().top + 1; this.angle = 1.5; this.minAngle = 1.35; this.maxAngle = 1.65; this.angleDelta = 0.01; this.speed = 2 + Math.random(); break; case "蝴蝶": this.el = cache.get( "./mtf/mtfWeatherPic/butterfly" + Math.floor(Math.random() * 5) + ".gif" ); this.el.size = (Math.random() * 10 + 8) | 0; this.common(this.el); this.maxLeft = w(this.el.size); this.maxTop = l(this.el.size); this.left = Math.random() * this.maxLeft; this.top = h().top + l() / Math.floor(1 + Math.random() * 3); this.angle = 0; this.minAngle = 0; this.maxAngle = 2; this.angleDelta = 0.01; this.speed = 2.5 + Math.random(); break; case "光": this.el = document.createElement("div"); this.el.size = (parseInt(Math.random() * 20) + 20) | 0; this.common(this.el); this.el.innerHTML = "●"; this.el.style.color = "#f9f7bd"; this.el.style.fontSize = this.el.size + "px"; this.maxLeft = w(this.el.size); this.maxTop = 99999999; this.left = Math.random() * this.maxLeft; this.top = h().top + l(this.el.size * 1.25); this.angle = 0.5; this.minAngle = 0.35; this.maxAngle = 0.65; this.angleDelta = 0.01 * Math.random(); this.speed = 2 + Math.random(); break; case "雨": this.el = document.createElement("div"); this.el.size = 12; this.common(this.el); this.el.innerHTML = "|"; this.el.style.color = "#FFFFFF"; this.el.style.fontSize = this.el.size + "px"; this.maxLeft = w(this.el.size); this.maxTop = l(this.el.size); this.left = Math.random() * this.maxLeft; this.top = h().top + 1; this.angle = 1.5; this.minAngle = -0.25; this.maxAngle = 0.25; this.angleDelta = 0.01 * Math.random(); this.speed = 5 + Math.random(); break; case "樱花": default: //默认樱花 this.el = cache.get( "./mtf/mtfWeatherPic/sakura" + Math.floor(Math.random() * 8) + ".gif" ); this.el.size = (Math.random() * 8 + 10) | 0; this.common(this.el); this.maxLeft = w(this.el.size); this.maxTop = l(this.el.size); this.left = Math.random() * this.maxLeft; this.top = h().top + 1; this.angle = 1.5; this.minAngle = 1.2; this.maxAngle = 1.7; this.angleDelta = 0.01 * Math.random(); this.speed = 2 + 1.5 * Math.random(); break; } if (os) { this.el.style.opacity = 0.5 + Math.random() * 0.5; } document.body.appendChild(this.el); }, move: function () { if (this.angle < this.minAngle || this.angle > this.maxAngle) { this.angleDelta = -this.angleDelta; } this.angle += this.angleDelta; this.left += this.speed * Math.cos(this.angle * Math.PI); this.top -= this.speed * Math.sin(this.angle * Math.PI); if (this.left < 0) { this.left = this.maxLeft; } else if (this.left > this.maxLeft) { this.left = 0; } }, draw: function () { this.el.style.top = Math.round(this.top) + "px"; this.el.style.left = Math.round(this.left) + "px"; if (os) { this.el.style.opacity -= 0.003; } }, remove: function () { document.body.removeChild(this.el); this.el = null; }, }; var cache = { h: Object.create(null), get: function (src) { var el = this.h[src]; if (!el) { el = document.createElement("img"); el.src = src; this.h[src] = el; } return el.cloneNode(); }, }; })(); ================================================ FILE: package.json ================================================ { "name": "yzhanweather", "version": "1.0.5", "description": "Pure CSS animation for sakura, rain, snow, firefly and butterfly effects, high performance without affecting SEO. 纯 CSS 动画实现樱花、雨、雪、萤火虫和蝴蝶飞舞背景效果,高性能且不影响 SEO", "main": "docs/yzhanweather.min.js", "files": [ "docs/yzhanweather.min.js" ], "scripts": { "build": "webpack --mode production", "dev": "webpack-dev-server" }, "repository": { "type": "git", "url": "git@mtf.github.com:mantoufan/yzhanWeather.git" }, "keywords": [ "sakura", "rain", "snow", "firefly", "butterfly", "pure", "css", "js", "library", "performance", "SEO" ], "author": "shonwu", "license": "MIT", "devDependencies": { "@swc/core": "^1.3.0", "swc-loader": "^0.2.3", "webpack": "^5.74.0", "webpack-cli": "^4.10.0", "webpack-dev-server": "^5.2.1" }, "dependencies": {} } ================================================ FILE: src/conf.js ================================================ const tpl = { container: { style: ` position: fixed; top: 0; width: 100%; height: 0; display: flex; justify-content: space-evenly; pointer-events: none; ` }, fall: { styles: [ `animation-delay: .1s; animation-duration: 4s`, `animation-delay: .5s; animation-duration: 5s`, `animation-delay: .3s; animation-duration: 3s; animation-name: {keyframes2}`, `animation-delay: .2s; animation-duration: 3.5s; animation-name: {keyframes1}`, `animation-delay: .9s; animation-duration: 3s; animation-name: {keyframes1}; animation-duration: 4.5s`, `animation-delay: .7s; animation-duration: 5.5s; animation-name: {keyframes1}`, `animation-delay: .4s; animation-duration: 3s`, `animation-delay: .6s; animation-duration: 4s; animation-name: {keyframes1}`, `animation-delay: .5s; animation-duration: 5.5s; animation-name: {keyframes2};`, `animation-delay: .3s;`, `animation-delay: .7s; animation-duration: 3.5s`, `animation-delay: .5s; animation-duration: 4s`, `animation-delay: .15s; animation-duration: 6.5s; animation-name: {keyframes2};`, `animation-delay: .5s; animation-duration: 6.5s; animation-name: {keyframes3};`, `animation-delay: .55s; animation-duration: 5.5s; animation-name: {keyframes3};`, `animation-delay: .75s; animation-duration: 7.5s; animation-name: {keyframes3};`, ], keyframes: [ `from { opacity: .9; } to { transform: translate(10vw, 100vh) rotateX(45deg); opacity: .2; }`, `from { opacity: .9; } to { transform: translate(-10vw, 100vh) rotateY(45deg); opacity: .2; }`, `from { opacity: .9; } to { transform: translate(5vw, 100vh) rotateX(-45deg); opacity: .2; }`, `from { opacity: .9; } to { transform: translate(-5vw, 100vh) rotateX(-45deg); opacity: .2; }` ] }, spread: { keyframes: [ `from { transform: translateY(50vh) rotateZ(55deg) skew(5deg, 5deg); opacity: .1; } to { transform: translate(10vw, -100%) rotateZ(45deg); opacity: .9; }`, `from { transform: translateY(50vh) rotateZ(-55deg) skew(5deg, 5deg); opacity: .1; } to { transform: translate(-10vw, -100%) rotateZ(-45deg); opacity: .9; }`, `from { transform: translateY(50vh) rotateZ(155deg) skew(-5deg, 5deg); opacity: .1; } to { transform: translate(10vw, 105vh) rotateZ(145deg); opacity: .9; }`, `from { transform: translateY(50vh) rotateZ(195deg) skew(-5deg, 5deg); opacity: .1; } to { transform: translate(-10vw, 105vh) rotateZ(185deg); opacity: .9; }`, ] }, snow: { style: ` width: .5vw; height: .5vw; border-radius: 50%; background-color: white; border: 1px solid #fefefe; transform: translateY(-100%); animation: {keyframes0} 6s linear infinite; `, } } export default { sakura: { num: 20, containerStyle: tpl.container.style, style: ` width: 1.5vw; height: 1.5vh; border-radius: 50% 0; background-image: linear-gradient(to right, pink, white); transform: translateY(-100%); animation: {keyframes0} 5s linear infinite; `, styles: tpl.fall.styles, keyframes: tpl.fall.keyframes }, snow: { num: 60, containerStyle: tpl.container.style, style: tpl.snow.style, styles: tpl.fall.styles, keyframes: tpl.fall.keyframes }, rain: { num: 60, containerStyle: tpl.container.style, style: ` width: .2vw; height: 1vw; border-radius: 30%; background-color: rgba(255, 255, 255, .5); transform: translateY(-100%); animation: {keyframes0} 6s ease-in-out infinite; `, styles: tpl.fall.styles, keyframes: tpl.fall.keyframes }, firefly: { num: 60, containerStyle: tpl.container.style, style: tpl.snow.style.replace('white', '#fff06b'), styles: tpl.fall.styles, keyframes: tpl.spread.keyframes }, butterfly: { num: 40, containerStyle: tpl.container.style, html: `
`, style: { '' : ` display: inline-block; position: relative; width: 1.25vw; height: 1.25vw; transform: translateY(-100%); animation: {keyframes0} 6s linear infinite; `, '::after': ` position: absolute; content: ' '; background-color: lightgoldenrodyellow; border-radius: 50%; display: block; left: .5vw; width: .25vw; height: .75vw; `, '.wing': ` position: absolute; width: .75vw; height: .75vw; animation: wing .3s infinite ease-in-out alternate; `, '.wing:last-child': ` right: 0; `, '.wing:last-child::after': ` right: .125vw; left: auto; `, '.wing::before': ` position: absolute; content: ' '; display: block; width: .6vw; height: .6vw; background-color: lightyellow; border-radius: 50%; `, '.wing::after': ` position: absolute; content: ' '; display: block; top: .5vw; left: .12vw; width: .5vw; height: .5vw; background-color: lightyellow; border-radius: 50%; ` }, keyframe: { 'wing': ` from { transform: scale(.9); } to { transform: scale(1.1); }` }, styles: tpl.fall.styles, keyframes: tpl.spread.keyframes } } ================================================ FILE: src/yzhanweather.js ================================================ import CONF from './conf' export default class { constructor() { this.wrapper = document.createElement('DIV') document.body.appendChild(this.wrapper) this.styleSheet = this.wrapper.appendChild(document.createElement('STYLE')).sheet this.container = this.wrapper.appendChild(document.createElement('DIV')) } uuid() { return 'xxxxxxxxxx'.replace(/x/g, _ => (Math.random() * 16 | 0).toString(16)) } createRules(rules, nameFn, ruleFn = v => v) { const names = [] for (const rule of rules) { const name = 'yz' + this.uuid() names.push(name) this.styleSheet.insertRule(`${nameFn(name)} { ${ruleFn(rule)} }`, 0) } return names } createRule(rule, nameFn) { for (const name in rule) { this.styleSheet.insertRule(`${nameFn(name)} { ${rule[name]} }`, 0) } } createKeyfarme(rule) { this.createRule(rule, n => '@keyframes ' + n) } createKeyfarmes(rules) { return this.createRules(rules, n => '@keyframes ' + n) } createStyles(rules, nameFn, keyframeNames) { return this.createRules(rules, nameFn, rule => rule.replace(/\{keyframes(\d+)\}/g, (_, p) => keyframeNames[p])) } processStyles(styles, fn) { const n = styles.length for (let i = 0; i < n; i++) { const g = styles[i].match(/animation-duration:(?.*)/) if (g === null) continue fn(parseFloat(g.groups.duration), g.groups.duration, i) } } replaceStyles(styles, config) { let maxDuration = 0 this.processStyles(styles, duration => maxDuration < duration && (maxDuration = duration)) this.processStyles(styles, (duration, srcDuration, i) => { const newDuration = (duration / maxDuration) * config.maxDuration const dstDuration = srcDuration.replace(duration, newDuration) styles[i] = styles[i].replace(srcDuration, dstDuration) }) } load(type, config) { let {num, html, containerStyle, style, styles, keyframe = {}, keyframes} = CONF[type] this.createKeyfarme(keyframe) const keyframeNames = this.createKeyfarmes(keyframes) if (typeof style === 'string') style = { '': style } this.createStyles([style['']], n => '.' + (this.container.className = n) + ' div', keyframeNames) this.createStyles([containerStyle], _ => '.' + this.container.className, keyframeNames) this.createRule(style, n => '.' + this.container.className + ' div' + (n[0] === ':' ? '' : ' ') + n) this.replaceStyles(styles, config) const classNames = this.createStyles(styles, n => ' .' + this.container.className + ' .' + n, keyframeNames) const fragement = document.createDocumentFragment() for (let i = 0; i < num; i++) { const div = document.createElement('DIV') if (html) div.innerHTML = html fragement.appendChild(div).className = classNames[i % classNames.length] } this.container.appendChild(fragement) } run(type, config = { maxDuration: 10 }) { this.clear() this.load(type, config) } clear() { this.container.innerHTML = '' while (this.styleSheet.cssRules.length) this.styleSheet.deleteRule(0) } destory() { this.wrapper.remove() this.wrapper = this.styleSheet = this.container = null } } ================================================ FILE: webpack.config.js ================================================ const { resolve } = require('path') module.exports = { mode: 'development', entry: './src/yzhanweather.js', output: { filename: 'yzhanweather.min.js', path: resolve('docs'), library: 'YZhanWeather', libraryTarget: 'umd', libraryExport: 'default', globalObject: 'this', environment: { arrowFunction: false, bigIntLiteral: false, const: false, destructuring: false, dynamicImport: false, forOf: false, module: false, optionalChaining: false, templateLiteral: false } }, module: { rules: [ { test: /\.js$/, include: resolve('src'), use: ['swc-loader'] } ] }, devServer: { hot: true, open: true, port: 3000, static: resolve('docs'), } }