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



[](https://github.com/mantoufan/yzhanweather/blob/main/LICENSE)

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

snow

firefly

rain

butterfly

## 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*

- With Pure CSS Version

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
sakura
firefly
snow
rain
butterfly
3s
6s
10s
15s
20s
================================================
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'),
}
}