欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > 下载使用nginx发布html自定义页面

下载使用nginx发布html自定义页面

2024/10/24 23:23:49 来源:https://blog.csdn.net/2302_80643218/article/details/139587169  浏览:    关键词:下载使用nginx发布html自定义页面

在浏览器搜索nginx.org,然后点击download,接着点击 stable and mainline

选择自己所使用系统对应的信息后点击(我用的是CentOS,所以需要点击RHEL and derivatives)

vim /etc/yum.repos.d/nginx.repo
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

保存退出后下载nginx

yum -y install nginx

修改nginx发布内容,vi/vim进入普通文件后可以使用gg来到首行首字符,再结合使用dG就可以将普通文件内的所有内容删除干净。(修改完成后可以使用systemctl start nginx启动nginx服务,别忘了服务器防火墙协议规则方面的限制调整和关闭selinux哦),最后再到浏览器访问搜索ip+端口,nginx端口号默认为80(可以修改),若并未修改过端口号则只输入ip即可访问,因为浏览器会默认访问该ip的80端口。

cd /usr/share/nginx/html
vim index.html

鼠标拖拽,装酷耍帅

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
<head>
<title>star</title>
<script type="text/javascript">
window.onload = function () {
C = Math.cos; // cache Math objects
S = Math.sin;
U = 0;
w = window;
j = document;
d = j.getElementById("c");
c = d.getContext("2d");
W = d.width = w.innerWidth;
H = d.height = w.innerHeight;
c.fillRect(0, 0, W, H); // resize <canvas> and draw black rect (default)
c.globalCompositeOperation = "lighter";  // switch to additive color application
c.lineWidth = 0.2;
c.lineCap = "round";
var bool = 0,
t = 0; // theta
d.onmousemove = function (e) {
if(window.T) {
if(D==9) { D=Math.random()*15; f(1); }
clearTimeout(T);
}
X = e.pageX; // grab mouse pixel coords
Y = e.pageY;
a=0; // previous coord.x
b=0; // previous coord.y
A = X, // original coord.x
B = Y; // original coord.y
R=(e.pageX/W * 999>>0)/999;
r=(e.pageY/H * 999>>0)/999;
U=e.pageX/H * 360 >>0;
D=9;
g = 360 * Math.PI / 180;
T = setInterval(f = function (e) { // start looping spectrum
c.save();
c.globalCompositeOperation = "source-over"; // switch to additive color application
if(e!=1) {
c.fillStyle = "rgba(0,0,0,0.02)";
c.fillRect(0, 0, W, H); // resize <canvas> and draw black rect (default)
}
c.restore();
i = 25; while(i --) {
c.beginPath();
if(D > 450 || bool) { // decrease diameter
if(!bool) { // has hit maximum
bool = 1;
}
if(D < 0.1) { // has hit minimum
bool = 0;
}
t -= g; // decrease theta
D -= 0.1; // decrease size
}
if(!bool) {
t += g; // increase theta
D += 0.1; // increase size
}
q = (R / r - 1) * t; // create hypotrochoid from current mouse position, and setup variables (see: http://en.wikipedia.org/wiki/Hypotrochoid)
x = (R - r) * C(t) + D * C(q) + (A + (X - A) * (i / 25)) + (r - R); // center on xy coords
y = (R - r) * S(t) - D * S(q) + (B + (Y - B) * (i / 25));
if (a) { // draw once two points are set
c.moveTo(a, b);
c.lineTo(x, y)
}
c.strokeStyle = "hsla(" + (U % 360) + ",100%,50%,0.75)"; // draw rainbow hypotrochoid
c.stroke();
a = x; // set previous coord.x
b = y; // set previous coord.y
}
U -= 0.5; // increment hue
A = X; // set original coord.x
B = Y; // set original coord.y
}, 16);
}
j.onkeydown = function(e) { a=b=0; R += 0.05 }
d.onmousemove({pageX:300, pageY:290})
}</script>
</head><body style="margin:0px;padding:0px;width:100%;height:100%;overflow:hidden;">
<canvas id="c"></canvas>
</body>
</html>

陪你去看流星雨

<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title><style>*{margin: 0;padding: 0;}body{background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);height: 100vh;        /* == height: 100%  */overflow: hidden;font-family: 'Times New Roman', Times, serif;justify-content: center;align-items: center;}.container{position: relative;margin:0px;width: 100%;height: 100%;-webkit-transform: rotate(45deg);transform: rotateZ(45deg);-webkit-animation: sky 200000ms linear infinite;animation: sky 200000ms linear infinite;}.meteor{position: absolute;center:50%;height: 2px;background: linear-gradient(-45deg, #5f91ff, rgba(0, 0, 255, 0));border-radius: 999px;-webkit-filter: drop-shadow(0 0 6px #699bff);filter: drop-shadow(0 0 6px #699bff);-webkit-animation: tail 3000ms ease-in-out infinite, shooting 3000ms ease-in-out infinite;animation: tail 3000ms ease-in-out infinite, shooting 3000ms ease-in-out infinite;}.meteor::before, .meteor::after{content: '';position: absolute;top: calc(50% - 1px);right: 0;height: 2px;background: linear-gradient(-45deg, rgba(0, 0, 255, 0), #5f91ff, rgba(0, 0, 255, 0) );-webkit-transform: translateX(50%) rotateZ(45deg);transform: translateX(50%) rotateZ(45deg);border-radius: 100%;-webkit-animation: shining 3000ms ease-in-out infinite;animation: shining 3000ms ease-in-out infinite;}.meteor::after{-webkit-transform: translateX(50%) rotateZ(-45deg);transform: translateX(50%) rotateZ(-45deg);}/* 1 */.meteor:nth-child(1){top: calc(50% - 185px);left: calc(50% - 150px);-webkit-animation-delay: 8888ms;animation-delay: 8888ms;}.meteor:nth-child(1)::before, .meteor:nth-child(1)::after, .meteor:nth-child(1)::after{-webkit-animation-delay: 8888ms;animation-delay: 8888ms;}/* 2 */.meteor:nth-child(2){top: calc(50% - 50px);left: calc(50% - 180px);-webkit-animation-delay: 9288ms;animation-delay: 9288ms;}.meteor:nth-child(2)::before, .meteor:nth-child(2)::after, .meteor:nth-child(2)::after{-webkit-animation-delay: 9288ms;animation-delay: 9288ms;}.meteor:nth-child(3){top: calc(50% - 145px);left: calc(50% - 135px);-webkit-animation-delay: 8600ms;animation-delay: 8600ms;}.meteor:nth-child(3)::before, .meteor:nth-child(3)::after, .meteor:nth-child(3)::after{-webkit-animation-delay: 8600ms;animation-delay: 8600ms;}.meteor:nth-child(4){top: calc(50% - 78px);left: calc(50% - 155px);-webkit-animation-delay: 3288ms;animation-delay: 3288ms;}.meteor:nth-child(4)::before, .meteor:nth-child(4)::after, .meteor:nth-child(4)::after{-webkit-animation-delay: 3288ms;animation-delay: 3288ms;}.meteor:nth-child(5){top: calc(50% - 183px);left: calc(50% - 8px);-webkit-animation-delay: 5588ms;animation-delay: 5588ms;}.meteor:nth-child(5)::before, .meteor:nth-child(5)::after, .meteor:nth-child(5)::after{-webkit-animation-delay: 5588ms;animation-delay: 5588ms;}.meteor:nth-child(6){top: calc(50% - 30px);left: calc(50% - 195px);-webkit-animation-delay: 9388ms;animation-delay: 9388ms;}.meteor:nth-child(6)::before, .meteor:nth-child(6)::after, .meteor:nth-child(6)::after{-webkit-animation-delay: 9388ms;animation-delay: 9388ms;}.meteor:nth-child(7){top: calc(50% - 95px);left: calc(50% - 70px);-webkit-animation-delay: 2588ms;animation-delay: 2588ms;}.meteor:nth-child(7)::before, .meteor:nth-child(7)::after, .meteor:nth-child(7)::after{-webkit-animation-delay: 2588ms;animation-delay: 2588ms;}.meteor:nth-child(8){top: calc(50% - 60px);left: calc(50% - 70px);-webkit-animation-delay: 5288ms;animation-delay: 5288ms;}.meteor:nth-child(8)::before, .meteor:nth-child(8)::after, .meteor:nth-child(8)::after{-webkit-animation-delay: 5288ms;animation-delay: 5288ms;}.meteor:nth-child(9){top: calc(50% - 75px);left: calc(50% - 250px);-webkit-animation-delay: 888ms;animation-delay: 888ms;}.meteor:nth-child(9)::before, .meteor:nth-child(9)::after, .meteor:nth-child(9)::after{-webkit-animation-delay: 888ms;animation-delay: 888ms;}.meteor:nth-child(9){top: calc(50% - 76px);left: calc(50% - 240px);-webkit-animation-delay: 2388ms;animation-delay: 2388ms;}.meteor:nth-child(9)::before, .meteor:nth-child(9)::after, .meteor:nth-child(9)::after{-webkit-animation-delay: 2388ms;animation-delay: 2388ms;}.meteor:nth-child(10){top: calc(50% - 85px);left: calc(50% - 6px);-webkit-animation-delay: 3588ms;animation-delay: 3588ms;}.meteor:nth-child(10)::before, .meteor:nth-child(10)::after, .meteor:nth-child(10)::after{-webkit-animation-delay: 3588ms;animation-delay: 3588ms;}.meteor:nth-child(11){top: calc(50% - 135px);left: calc(50% - 260px);-webkit-animation-delay: 2888ms;animation-delay: 2888ms;}.meteor:nth-child(11)::before, .meteor:nth-child(11)::after, .meteor:nth-child(11)::after{-webkit-animation-delay: 2888ms;animation-delay: 2888ms;}.meteor:nth-child(12){top: calc(50% - 15px);left: calc(50% - 8px);-webkit-animation-delay: 388ms;animation-delay: 388ms;}.meteor:nth-child(12)::before, .meteor:nth-child(12)::after, .meteor:nth-child(12)::after{-webkit-animation-delay: 388ms;animation-delay: 388ms;}.meteor:nth-child(13){top: calc(50% - 155px);left: calc(50% - 50px);-webkit-animation-delay: 7288ms;animation-delay: 7288ms;}.meteor:nth-child(13)::before, .meteor:nth-child(13)::after, .meteor:nth-child(13)::after{-webkit-animation-delay: 7288ms;animation-delay: 7288ms;}.meteor:nth-child(14){top: calc(50% - 28px);left: calc(50% - 80px);-webkit-animation-delay: 8888ms;animation-delay: 8888ms;}.meteor:nth-child(14)::before, .meteor:nth-child(14)::after, .meteor:nth-child(14)::after{-webkit-animation-delay: 8888ms;animation-delay: 8888ms;}.meteor:nth-child(15){top: calc(50% - 35px);left: calc(50% - 200px);-webkit-animation-delay: 7588ms;animation-delay: 7588ms;}.meteor:nth-child(15)::before, .meteor:nth-child(15)::after, .meteor:nth-child(15)::after{-webkit-animation-delay: 7588ms;animation-delay: 7588ms;}.meteor:nth-child(16){top: calc(50% - 40px);left: calc(50% - 250px);-webkit-animation-delay: 1888ms;animation-delay: 1888ms;}.meteor:nth-child(16)::before, .meteor:nth-child(16)::after, .meteor:nth-child(16)::after{-webkit-animation-delay: 1888ms;animation-delay: 1888ms;}@-webkit-keyframes tail{0%{width: 0;}30%{width: 100px;}100%{width: 0;}}@keyframes tail{0%{width: 0;}30%{width: 100px;}100%{width: 0;}}@-webkit-keyframes shining{0%{width: 0;}50%{width: 30px;}1000%{width: 0;}}@keyframes shining{0%{width: 0;}50%{width: 30px;}1000%{width: 0;}}@-webkit-keyframes shooting{0%{-webkit-transform: translateX(0);transform: translateX(0);}100%{-webkit-transform: translateX(300px);transform: translateX(300px);}}@keyframes shooting{0%{-webkit-transform: translateX(0);transform: translateX(0);}100%{-webkit-transform: translateX(300px);transform: translateX(300px);}}@-webkit-keyframes sky{0%{-webkit-transform: rotate(45deg);transform: rotate(45deg);}100%{-webkit-transform: rotate(405deg);transform: rotate(405deg);}}@keyframes sky{0%{-webkit-transform: rotate(45deg);transform: rotate(45deg);}100%{-webkit-transform: rotate(405deg);transform: rotate(405deg);}}</style></head><body><div class="container"><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div><div class="meteor"></div></div></body></html>

逼真烟花

<!DOCTYPE html>
<html lang="en" >
<head><meta charset="UTF-8"><title>2024新年快乐!万事如意!</title><meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#000000">
<link rel="shortcut icon" type="image/png" href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.png">
<link rel="icon" type="image/png" href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.png">
<link rel="apple-touch-icon-precomposed" href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.png">
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.png">
<link href="https://fonts.googleapis.com/css?family=Russo+One" rel="stylesheet"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<link rel="stylesheet" href="./style.css">
<style>
* {position: relative;box-sizing: border-box;
}html,body {height: 100%;
}html {background-color: #000;
}body {overflow: hidden;color: rgba(255, 255, 255, 0.5);font-family: "Russo One", arial, sans-serif;line-height: 1.25;letter-spacing: 0.06em;
}.hide {opacity: 0;visibility: hidden;
}.remove {display: none;
}.blur {filter: blur(12px);
}.container {height: 100%;display: flex;justify-content: center;align-items: center;
}#loading-init {width: 100%;align-self: center;text-align: center;font-size: 2em;
}#stage-container {overflow: hidden;box-sizing: initial;border: 1px solid #222;margin: -1px;
}#canvas-container {width: 100%;height: 100%;transition: filter 0.3s;}
#canvas-container canvas {position: absolute;mix-blend-mode: lighten;
}#controls {position: absolute;top: 0;width: 100%;padding-bottom: 50px;display: flex;justify-content: space-between;transition: opacity 0.3s, visibility 0.3s;
}
@media (min-width: 800px) {#controls {visibility: visible;}#controls.hide:hover {opacity: 1;}
}#menu {display: flex;flex-direction: column;justify-content: center;align-items: center;position: absolute;top: 0;bottom: 0;width: 100%;background-color: rgba(0, 0, 0, 0.42);transition: opacity 0.3s, visibility 0.3s;
}
#menu__header {padding: 20px 0 44px;font-size: 2em;text-transform: uppercase;
}
#menu form {width: 240px;padding: 0 20px;overflow: auto;
}
#menu .form-option {margin: 20px 0;
}
#menu .form-option label {text-transform: uppercase;
}
#menu .form-option--select label {display: block;margin-bottom: 6px;
}
#menu .form-option--select select {display: block;width: 100%;height: 30px;font-size: 1rem;font-family: "Russo One", arial, sans-serif;color: rgba(255, 255, 255, 0.5);letter-spacing: 0.06em;background-color: transparent;border: 1px solid rgba(255, 255, 255, 0.5);
}
#menu .form-option--select select option {background-color: black;
}
#menu .form-option--checkbox label {display: flex;align-items: center;transition: opacity 0.3s;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;
}
#menu .form-option--checkbox input {display: block;width: 20px;height: 20px;margin-right: 8px;opacity: 0.5;
}
@media (max-width: 800px) {#menu .form-option select, #menu .form-option input {outline: none;}
}#close-menu-btn {position: absolute;top: 0;right: 0;
}.btn {opacity: 0.16;width: 44px;height: 44px;display: flex;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;cursor: default;transition: opacity 0.3s;
}
.btn--bright {opacity: 0.5;
}
@media (min-width: 800px) {.btn:hover {opacity: 0.32;}.btn--bright:hover {opacity: 0.75;}
}
.btn svg {display: block;margin: auto;
}
</style>
</head>
<body>
<!-- partial:index.partial.html -->
<!-- SVG Spritesheet -->
<div style="height: 0; width: 0; position: absolute; visibility: hidden;">
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="icon-play" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</symbol>
<symbol id="icon-pause" viewBox="0 0 24 24">
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
</symbol>
<symbol id="icon-close" viewBox="0 0 24 24">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
</symbol>
<symbol id="icon-settings" viewBox="0 0 24 24">
<path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/>
</symbol>
<symbol id="icon-shutter-fast" viewBox="0 0 24 24">
<path d="M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z"/>
</symbol>
<symbol id="icon-shutter-slow" viewBox="0 0 24 24">
<path d="M1 5h2v14H1zm4 0h2v14H5zm17 0H10c-.55 0-1 .45-1 1v12c0 .55.45 1 1 1h12c.55 0 1-.45 1-1V6c0-.55-.45-1-1-1zM11 17l2.5-3.15L15.29 16l2.5-3.22L21 17H11z"/>
</symbol>
</svg>
</div><!-- App -->
<div class="container">
<div id="loading-init">Loading...</div>
<div id="stage-container" class="remove">
<div id="canvas-container">
<canvas id="trails-canvas"></canvas>
<canvas id="main-canvas"></canvas>
</div>
<div id="controls">
<div id="pause-btn" class="btn">
<svg fill="white" width="24" height="24"><use href="#icon-pause"></use></svg>
</div>
<div id="shutter-btn" class="btn">
<svg fill="white" width="24" height="24"><use href="#icon-shutter-slow"></use></svg>
</div>
<div id="settings-btn" class="btn">
<svg fill="white" width="24" height="24"><use href="#icon-settings"></use></svg>
</div>
</div>
<div id="menu" class="hide">
<div id="close-menu-btn" class="btn btn--bright">
<svg fill="white" width="24" height="24"><use href="#icon-close"></use></svg>
</div>
<div id="menu__header">Settings</div>
<form>
<div class="form-option form-option--select">
<label>Shell Type</label>
<select id="shell-type"></select>
</div>
<div class="form-option form-option--select">
<label>Shell Size</label>
<select id="shell-size"></select>
</div>
<div class="form-option form-option--checkbox">
<label id="auto-launch-label"><input id="auto-launch" type="checkbox" /><span>Auto Fire</span></label>
</div>
<div class="form-option form-option--checkbox">
<label id="finale-mode-label"><input id="finale-mode" type="checkbox" /><span>Finale Mode</span></label>
</div>
<div class="form-option form-option--checkbox">
<label id="hide-controls-label"><input id="hide-controls" type="checkbox" /><span>Hide Controls</span></label>
</div>
</form>
</div>
</div>
</div>
<!-- partial --><script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/fscreen%401.0.1.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/Stage%400.1.4.js'></script>
<script src='https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/MyMath.js'></script>
<script>
'use strict';
console.clear();const IS_MOBILE = window.innerWidth <= 640;
const IS_DESKTOP = window.innerWidth > 800;
const IS_HEADER = IS_DESKTOP && window.innerHeight < 300;
// 8K - can restrict this if needed
const MAX_WIDTH = 7680;
const MAX_HEIGHT = 4320;
const GRAVITY = 0.9; // Acceleration in px/s
let simSpeed = 1;const COLOR = {
Red: '#ff0043',
Green: '#14fc56',
Blue: '#1e7fff',
Purple: '#e60aff',
Gold: '#ffae00',
White: '#ffffff'
};// Special invisible color (not rendered, and therefore not in COLOR map)
const INVISIBLE = '_INVISIBLE_';// Interactive state management
const store = {
_listeners: new Set(),
_dispatch() {
this._listeners.forEach(listener => listener(this.state))
},
state: {
paused: false,
longExposure: false,
menuOpen: false,
config: {
shell: 'Random',
size: IS_DESKTOP && !IS_HEADER ? '3' : '1',
autoLaunch: true,
finale: false,
hideControls: IS_HEADER
}
},
setState(nextState) {
this.state = Object.assign({}, this.state, nextState);
this._dispatch();
this.persist();
},
subscribe(listener) {
this._listeners.add(listener);
return () => this._listeners.remove(listener);
},
// Load / persist select state to localStorage
load() {
if (localStorage.getItem('schemaVersion') === '1') {
this.state.config.size = JSON.parse(localStorage.getItem('configSize'));
this.state.config.hideControls = JSON.parse(localStorage.getItem('hideControls'));
}
},
persist() {
localStorage.setItem('schemaVersion', '1');
localStorage.setItem('configSize', JSON.stringify(this.state.config.size));
localStorage.setItem('hideControls', JSON.stringify(this.state.config.hideControls));
}
};if (!IS_HEADER) {
store.load();
}// Actions
// ---------function togglePause(toggle) {
if (typeof toggle === 'boolean') {
store.setState({ paused: toggle });
} else {
store.setState({ paused: !store.state.paused });
}
}function toggleLongExposure(toggle) {
if (typeof toggle === 'boolean') {
store.setState({ longExposure: toggle });
} else {
store.setState({ longExposure: !store.state.longExposure });
}
}function toggleMenu(toggle) {
if (typeof toggle === 'boolean') {
store.setState({ menuOpen: toggle });
} else {
store.setState({ menuOpen: !store.state.menuOpen });
}
}function updateConfig(nextConfig) {
nextConfig = nextConfig || getConfigFromDOM();
store.setState({
config: Object.assign({}, store.state.config, nextConfig)
});
}// Selectors
// -----------const canInteract = () => !store.state.paused && !store.state.menuOpen;
const shellNameSelector = () => store.state.config.shell;
// Converts shell size to number.
const shellSizeSelector = () => +store.state.config.size;
const finaleSelector = () => store.state.config.finale;// Render app UI / keep in sync with state
const appNodes = {
stageContainer: '#stage-container',
canvasContainer: '#canvas-container',
controls: '#controls',
menu: '#menu',
pauseBtn: '#pause-btn',
pauseBtnSVG: '#pause-btn use',
shutterBtn: '#shutter-btn',
shutterBtnSVG: '#shutter-btn use',
shellType: '#shell-type',
shellSize: '#shell-size',
autoLaunch: '#auto-launch',
autoLaunchLabel: '#auto-launch-label',
finaleMode: '#finale-mode',
finaleModeLabel: '#finale-mode-label',
hideControls: '#hide-controls',
hideControlsLabel: '#hide-controls-label'
};// Convert appNodes selectors to dom nodes
Object.keys(appNodes).forEach(key => {
appNodes[key] = document.querySelector(appNodes[key]);
});// Remove loading state
document.getElementById('loading-init').remove();
appNodes.stageContainer.classList.remove('remove');// First render is called in init()
function renderApp(state) {
appNodes.pauseBtnSVG.setAttribute('href', `#icon-${state.paused ? 'play' : 'pause'}`);
appNodes.shutterBtnSVG.setAttribute('href', `#icon-shutter-${state.longExposure ? 'fast' : 'slow'}`);
appNodes.controls.classList.toggle('hide', state.menuOpen || state.config.hideControls);
appNodes.canvasContainer.classList.toggle('blur', state.menuOpen);
appNodes.menu.classList.toggle('hide', !state.menuOpen);
appNodes.finaleModeLabel.style.opacity = state.config.autoLaunch ? 1 : 0.32;
appNodes.shellType.value = state.config.shell;
appNodes.shellSize.value = state.config.size;
appNodes.autoLaunch.checked = state.config.autoLaunch;
appNodes.finaleMode.checked = state.config.finale;
appNodes.hideControls.checked = state.config.hideControls;
}store.subscribe(renderApp);function getConfigFromDOM() {
return {
shell: appNodes.shellType.value,
size: appNodes.shellSize.value,
autoLaunch: appNodes.autoLaunch.checked,
finale: appNodes.finaleMode.checked,
hideControls: appNodes.hideControls.checked
};
};const updateConfigNoEvent = () => updateConfig();
appNodes.shellType.addEventListener('input', updateConfigNoEvent);
appNodes.shellSize.addEventListener('input', updateConfigNoEvent);
appNodes.autoLaunchLabel.addEventListener('click', () => setTimeout(updateConfig, 0));
appNodes.finaleModeLabel.addEventListener('click', () => setTimeout(updateConfig, 0));
appNodes.hideControlsLabel.addEventListener('click', () => setTimeout(updateConfig, 0));// Constant derivations
const COLOR_NAMES = Object.keys(COLOR);
const COLOR_CODES = COLOR_NAMES.map(colorName => COLOR[colorName]);
// Invisible stars need an indentifier, even through they won't be rendered - physics still apply.
const COLOR_CODES_W_INVIS = [...COLOR_CODES, INVISIBLE];
// Tuples is a map keys by color codes (hex) with values of { r, g, b } tuples (still just objects).
const COLOR_TUPLES = {};
COLOR_CODES.forEach(hex => {
COLOR_TUPLES[hex] = {
r: parseInt(hex.substr(1, 2), 16),
g: parseInt(hex.substr(3, 2), 16),
b: parseInt(hex.substr(5, 2), 16),
};
});// Get a random color.
function randomColorSimple() {
return COLOR_CODES[Math.random() * COLOR_CODES.length | 0];
}// Get a random color, with some customization options available.
let lastColor;
function randomColor(options) {
const notSame = options && options.notSame;
const notColor = options && options.notColor;
const limitWhite = options && options.limitWhite;
let color = randomColorSimple();
// limit the amount of white chosen randomly
if (limitWhite && color === COLOR.White && Math.random() < 0.6) {
color = randomColorSimple();
}
if (notSame) {
while (color === lastColor) {
color = randomColorSimple();
}
}
else if (notColor) {
while (color === notColor) {
color = randomColorSimple();
}
}
lastColor = color;
return color;
}function whiteOrGold() {
return Math.random() < 0.5 ? COLOR.Gold : COLOR.White;
}const PI_2 = Math.PI * 2;
const PI_HALF = Math.PI * 0.5;const trailsStage = new Stage('trails-canvas');
const mainStage = new Stage('main-canvas');
const stages = [
trailsStage,
mainStage
];// Fill trails canvas with black to start.
trailsStage.ctx.fillStyle = '#000';
trailsStage.ctx.fillRect(0, 0, trailsStage.width, trailsStage.height);// Fullscreen helpers, using Fscreen for prefixes
function requestFullscreen() {
if (fullscreenEnabled() && !isFullscreen()) {
fscreen.requestFullscreen(document.documentElement);
}
}function fullscreenEnabled() {
return fscreen.fullscreenEnabled;
}function isFullscreen() {
return !!fscreen.fullscreenElement;
}// Shell helpers
function makePistilColor(shellColor) {
return (shellColor === COLOR.White || shellColor === COLOR.Gold) ? randomColor({ notColor: shellColor }) : whiteOrGold();
}// Unique shell types
//生成菊花状的烟花效果
const crysanthemumShell = (size=1) => {
const glitter = Math.random() < 0.25;//是否产生闪光效果
const singleColor = Math.random() < 0.68;//是否使用单一颜色
//一个颜色数组,包含1到2个颜色值。如果singleColor为真,则该数组仅包含一个颜色;否则该数组将包含两个不同的颜色。颜色值通过调用randomColor函数随机生成
const color = singleColor ? randomColor({ limitWhite: true }) : [randomColor(), randomColor({ notSame: true })];
const pistil = singleColor && Math.random() < 0.42;//是否绘制花蕊
const pistilColor = makePistilColor(color);//绘制花蕊,花蕊的颜色
const streamers = !pistil && color !== COLOR.White && Math.random() < 0.42;//是否绘制流星效果
return {
size: 300 + size * 100,//烟花的大小
starLife: 900 + size * 200,//星星效果的寿命
starDensity: glitter ? 1.1 : 1.5,//星星效果的密度
color,
glitter: glitter ? 'light' : '',//闪光效果的类型
glitterColor: whiteOrGold(),//绘制闪光效果
pistil,
pistilColor,
streamers
};
};//生成棕榈树状的烟花效果
const palmShell = (size=1) => ({
size: 250 + size * 75,
starDensity: 0.6,
starLife: 1800 + size * 200,
glitter: 'heavy'
});//用于生成环状的烟花效果
const ringShell = (size=1) => {
const color = randomColor();
const pistil = Math.random() < 0.75;
return {
ring: true,
color,
size: 300 + size * 100,
starLife: 900 + size * 200,
starCount: 2.2 * PI_2 * (size+1),
pistil,
pistilColor: makePistilColor(color),
glitter: !pistil ? 'light' : '',
glitterColor: color === COLOR.Gold ? COLOR.Gold : COLOR.White
};
};//生成十字状的烟花效果
const crossetteShell = (size=1) => {
const color = randomColor({ limitWhite: true });
return {
size: 300 + size * 100,
starLife: 900 + size * 200,
starLifeVariation: 0.22,
color,
crossette: true,
pistil: Math.random() < 0.5,
pistilColor: makePistilColor(color)
};
};//生成花朵状的烟花效果
const floralShell = (size=1) => ({
size: 300 + size * 120,
starDensity: 0.38,
starLife: 500 + size * 50,
starLifeVariation: 0.5,
color: Math.random() < 0.65 ? 'random' : (Math.random() < 0.15 ? randomColor() : [randomColor(), randomColor({ notSame: true })]),
floral: true
});//生成落叶状的烟花效果
const fallingLeavesShell = (size=1) => ({
color: INVISIBLE,
size: 300 + size * 120,
starDensity: 0.38,
starLife: 500 + size * 50,
starLifeVariation: 0.5,
glitter: 'medium',
glitterColor: COLOR.Gold,
fallingLeaves: true
});//生成柳树状烟花效果
const willowShell = (size=1) => ({
size: 300 + size * 100,
starDensity: 0.7,
starLife: 3000 + size * 300,
glitter: 'willow',
glitterColor: COLOR.Gold,
color: INVISIBLE
});//生成爆裂声烟花(crackleShell)效果
const crackleShell = (size=1) => {
// favor gold
const color = Math.random() < 0.75 ? COLOR.Gold : randomColor();
return {
size: 380 + size * 75,
starDensity: 1,
starLife: 600 + size * 100,
starLifeVariation: 0.32,
glitter: 'light',
glitterColor: COLOR.Gold,
color,
crackle: true,
pistil: Math.random() < 0.65,
pistilColor: makePistilColor(color)
};
};//马尾状烟花效果
const horsetailShell = (size=1) => {
const color = randomColor();
return {
horsetail: true,
color,
size: 250 + size * 38,
starDensity: 0.85 + size * 0.1,
starLife: 2500 + size * 300,
glitter: 'medium',
glitterColor: Math.random() < 0.5 ? whiteOrGold() : color
};
};function randomShellName() {
return Math.random() < 0.6 ? 'Crysanthemum' : shellNames[(Math.random() * (shellNames.length - 1) + 1) | 0 ];
}function randomShell(size) {
return shellTypes[randomShellName()](size);
}function shellFromConfig(size) {
return shellTypes[shellNameSelector()](size);
}// Get a random shell, not including processing intensive varients
// Note this is only random when "Random" shell is selected in config.
// Also, this does not create the shell, only returns the factory function.
const fastShellBlacklist = ['Falling Leaves', 'Floral', 'Willow'];
function randomFastShell() {
const isRandom = shellNameSelector() === 'Random';
let shellName = isRandom ? randomShellName() : shellNameSelector();
if (isRandom) {
while (fastShellBlacklist.includes(shellName)) {
shellName = randomShellName();
}
}
return shellTypes[shellName];
}const shellTypes = {
'Random': randomShell,
'Crackle': crackleShell,
'Crossette': crossetteShell,
'Crysanthemum': crysanthemumShell,
'Falling Leaves': fallingLeavesShell,
'Floral': floralShell,
'Horse Tail': horsetailShell,
'Palm': palmShell,
'Ring': ringShell,
'Willow': willowShell
};const shellNames = Object.keys(shellTypes);function fitShellPositionInBoundsH(position) {
const edge = 0.18;
return (1 - edge*2) * position + edge;
}function fitShellPositionInBoundsV(position) {
return position * 0.75;
}function getRandomShellPositionH() {
return fitShellPositionInBoundsH(Math.random());
}function getRandomShellPositionV() {
return fitShellPositionInBoundsV(Math.random());
}function getRandomShellSize() {
const baseSize = shellSizeSelector();
const maxVariance = Math.min(2.5, baseSize);
const variance = Math.random() * maxVariance;
const size = baseSize - variance;
const height = maxVariance === 0 ? Math.random() : 1 - (variance / maxVariance);
const centerOffset = Math.random() * (1 - height * 0.65) * 0.5;
const x = Math.random() < 0.5 ? 0.5 - centerOffset : 0.5 + centerOffset;
return {
size,
x: fitShellPositionInBoundsH(x),
height: fitShellPositionInBoundsV(height)
};
}// Launches a shell from a user pointer event, based on state.config
function launchShellFromConfig(event) {
const shell = new Shell(shellFromConfig(shellSizeSelector()));
const w = mainStage.width;
const h = mainStage.height;
shell.launch(
event ? event.x / w : getRandomShellPositionH(),
event ? 1 - event.y / h : getRandomShellPositionV()
);
}// Sequences
// -----------function seqRandomShell() {
const size = getRandomShellSize();
const shell = new Shell(shellFromConfig(size.size));
shell.launch(size.x, size.height);
let extraDelay = shell.starLife;
if (shell.fallingLeaves) {
extraDelay = 4000;
}
return 900 + Math.random() * 600 + extraDelay;
}function seqTwoRandom() {
const size1 = getRandomShellSize();
const size2 = getRandomShellSize();
const shell1 = new Shell(shellFromConfig(size1.size));
const shell2 = new Shell(shellFromConfig(size2.size));
const leftOffset = Math.random() * 0.2 - 0.1;
const rightOffset = Math.random() * 0.2 - 0.1;
shell1.launch(0.3 + leftOffset, size1.height);
shell2.launch(0.7 + rightOffset, size2.height);
let extraDelay = Math.max(shell1.starLife, shell2.starLife);
if (shell1.fallingLeaves || shell2.fallingLeaves) {
extraDelay = 4000;
}
return 900 + Math.random() * 600 + extraDelay;
}function seqTriple() {
const shellType = randomFastShell();
const baseSize = shellSizeSelector();
const smallSize = Math.max(0, baseSize - 1.25);
const offset = Math.random() * 0.08 - 0.04;
const shell1 = new Shell(shellType(baseSize));
shell1.launch(0.5 + offset, 0.7);
const leftDelay = 1000 + Math.random() * 400;
const rightDelay = 1000 + Math.random() * 400;
setTimeout(() => {
const offset = Math.random() * 0.08 - 0.04;
const shell2 = new Shell(shellType(smallSize));
shell2.launch(0.2 + offset, 0.1);
}, leftDelay);
setTimeout(() => {
const offset = Math.random() * 0.08 - 0.04;
const shell3 = new Shell(shellType(smallSize));
shell3.launch(0.8 + offset, 0.1);
}, rightDelay);
return 4000;
}function seqSmallBarrage() {
seqSmallBarrage.lastCalled = Date.now();
const barrageCount = IS_DESKTOP ? 11 : 5;
const shellSize = Math.max(0, shellSizeSelector() - 2);
const useCrysanthemum = Math.random() < 0.7;
// (cos(x*5π+0.5π)+1)/2 is a custom wave bounded by 0 and 1 used to set varying launch heights
function launchShell(x) {
const isRandom = shellNameSelector() === 'Random';
let shellType = isRandom ? (useCrysanthemum ? crysanthemumShell : randomFastShell()) : shellTypes[shellNameSelector()];
const shell = new Shell(shellType(shellSize));
const height = (Math.cos(x*5*Math.PI + PI_HALF) + 1) / 2;
shell.launch(x, height * 0.75);
}
let count = 0;
let delay = 0;
while(count < barrageCount) {
if (count === 0) {
launchShell(0.5)
count += 1;
}
else {
const offset = (count + 1) / barrageCount / 2;
setTimeout(() => {
launchShell(0.5 + offset);
launchShell(0.5 - offset);
}, delay);
count += 2;
}
delay += 200;
}
return 3400 + barrageCount * 120;
}
seqSmallBarrage.cooldown = 15000;
seqSmallBarrage.lastCalled = Date.now();const sequences = [
seqRandomShell,
seqTwoRandom,
seqTriple,
seqSmallBarrage
];let isFirstSeq = true;
const finaleCount = 32;
let currentFinaleCount = 0;
function startSequence() {
if (isFirstSeq) {
isFirstSeq = false;
const shell = new Shell(crysanthemumShell(shellSizeSelector()));
shell.launch(0.5, 0.5);
return 2400;
}
if (finaleSelector()) {
seqRandomShell();
if (currentFinaleCount < finaleCount) {
currentFinaleCount++;
return 170;
}
else {
currentFinaleCount = 0;
return 6000;
}
}
const rand = Math.random();
if (rand < 0.2 && Date.now() - seqSmallBarrage.lastCalled > seqSmallBarrage.cooldown) {
return seqSmallBarrage();
}
if (rand < 0.6) {
return seqRandomShell();
}
else if (rand < 0.8) {
return seqTwoRandom();
}
else if (rand < 1) {
return seqTriple();
}
}let activePointerCount = 0;
let isUpdatingSpeed = false;function handlePointerStart(event) {
activePointerCount++;
const btnSize = 44;
if (event.y < btnSize) {
if (event.x < btnSize) {
togglePause();
return;
}
if (event.x > mainStage.width/2 - btnSize/2 && event.x < mainStage.width/2 + btnSize/2) {
toggleLongExposure();
return;
}
if (event.x > mainStage.width - btnSize) {
toggleMenu();
return;
}
}
if (!canInteract()) return;
if (updateSpeedFromEvent(event)) {
isUpdatingSpeed = true;
}
else if (event.onCanvas) {
launchShellFromConfig(event);
}
}function handlePointerEnd(event) {
activePointerCount--;
isUpdatingSpeed = false;
}function handlePointerMove(event) {
if (!canInteract()) return;
if (isUpdatingSpeed) {
updateSpeedFromEvent(event);
}
}function handleKeydown(event) {
// P
if (event.keyCode === 80) {
togglePause();
}
// O
else if (event.keyCode === 79) {
toggleMenu();
}
// Esc
else if (event.keyCode === 27) {
toggleMenu(false);
}
}mainStage.addEventListener('pointerstart', handlePointerStart);
mainStage.addEventListener('pointerend', handlePointerEnd);
mainStage.addEventListener('pointermove', handlePointerMove);
window.addEventListener('keydown', handleKeydown);
// Try to go fullscreen upon a touch
window.addEventListener('touchend', (event) => !IS_DESKTOP && requestFullscreen());function handleResize() {
const w = window.innerWidth;
const h = window.innerHeight;
// Try to adopt screen size, heeding maximum sizes specified
const containerW = Math.min(w, MAX_WIDTH);
// On small screens, use full device height
const containerH = w <= 420 ? h : Math.min(h, MAX_HEIGHT);
appNodes.stageContainer.style.width = containerW + 'px';
appNodes.stageContainer.style.height = containerH + 'px';
stages.forEach(stage => stage.resize(containerW, containerH));
}// Compute initial dimensions
handleResize();window.addEventListener('resize', handleResize);// Dynamic globals
let speedBarOpacity = 0;
let autoLaunchTime = 0;function updateSpeedFromEvent(event) {
if (isUpdatingSpeed || event.y >= mainStage.height - 44) {
// On phones it's hard to hit the edge pixels in order to set speed at 0 or 1, so some padding is provided to make that easier.
const edge = 16;
const newSpeed = (event.x - edge) / (mainStage.width - edge * 2);
simSpeed = Math.min(Math.max(newSpeed, 0), 1);
// show speed bar after an update
speedBarOpacity = 1;
// If we updated the speed, return true
return true;
}
// Return false if the speed wasn't updated
return false;
}// Extracted function to keep `update()` optimized
function updateGlobals(timeStep, lag) {
// Always try to fade out speed bar
if (!isUpdatingSpeed) {
speedBarOpacity -= lag / 30; // half a second
if (speedBarOpacity < 0) {
speedBarOpacity = 0;
}
}
// auto launch shells
if (store.state.config.autoLaunch) {
autoLaunchTime -= timeStep;
if (autoLaunchTime <= 0) {
autoLaunchTime = startSequence();
}
}
}function update(frameTime, lag) {
if (!canInteract()) return;
const { width, height } = mainStage;
const timeStep = frameTime * simSpeed;
const speed = simSpeed * lag;
updateGlobals(timeStep, lag);
const starDrag = 1 - (1 - Star.airDrag) * speed;
const starDragHeavy = 1 - (1 - Star.airDragHeavy) * speed;
const sparkDrag = 1 - (1 - Spark.airDrag) * speed;
const gAcc = timeStep / 1000 * GRAVITY;
COLOR_CODES_W_INVIS.forEach(color => {
// Stars
Star.active[color].forEach((star, i, stars) => {
star.life -= timeStep;
if (star.life <= 0) {
stars.splice(i, 1);
Star.returnInstance(star);
} else {
star.prevX = star.x;
star.prevY = star.y;
star.x += star.speedX * speed;
star.y += star.speedY * speed;
// Apply air drag if star isn't "heavy". The heavy property is used for the shell comets.
if (!star.heavy) {
star.speedX *= starDrag;
star.speedY *= starDrag;
}
else {
star.speedX *= starDragHeavy;
star.speedY *= starDragHeavy;
}
star.speedY += gAcc;
if (star.spinRadius) {
star.spinAngle += star.spinSpeed * speed;
star.x += Math.sin(star.spinAngle) * star.spinRadius * speed;
star.y += Math.cos(star.spinAngle) * star.spinRadius * speed;
}
if (star.sparkFreq) {
star.sparkTimer -= timeStep;
while (star.sparkTimer < 0) {
star.sparkTimer += star.sparkFreq;
Spark.add(
star.x,
star.y,
star.sparkColor,
Math.random() * PI_2,
Math.random() * star.sparkSpeed,
star.sparkLife * 0.8 + Math.random() * star.sparkLifeVariation * star.sparkLife
);
}
}
}
});
// Sparks
Spark.active[color].forEach((spark, i, sparks) => {
spark.life -= timeStep;
if (spark.life <= 0) {
sparks.splice(i, 1);
Spark.returnInstance(spark);
} else {
spark.prevX = spark.x;
spark.prevY = spark.y;
spark.x += spark.speedX * speed;
spark.y += spark.speedY * speed;
spark.speedX *= sparkDrag;
spark.speedY *= sparkDrag;
spark.speedY += gAcc;
}
});
});
render(speed);
}function render(speed) {
const { dpr, width, height } = mainStage;
const trailsCtx = trailsStage.ctx;
const mainCtx = mainStage.ctx;
colorSky(speed);
trailsCtx.scale(dpr, dpr);
mainCtx.scale(dpr, dpr);
trailsCtx.globalCompositeOperation = 'source-over';
trailsCtx.fillStyle = `rgba(0, 0, 0, ${store.state.longExposure ? 0.0025 : 0.1 * speed})`;
trailsCtx.fillRect(0, 0, width, height);
// Remaining drawing on trails canvas will use 'lighten' blend mode
trailsCtx.globalCompositeOperation = 'lighten';
mainCtx.clearRect(0, 0, width, height);
// Draw queued burst flashes
while (BurstFlash.active.length) {
const bf = BurstFlash.active.pop();
const burstGradient = trailsCtx.createRadialGradient(bf.x, bf.y, 0, bf.x, bf.y, bf.radius);
burstGradient.addColorStop(0.05, 'white');
burstGradient.addColorStop(0.25, 'rgba(255, 160, 20, 0.2)');
burstGradient.addColorStop(1, 'rgba(255, 160, 20, 0)');
trailsCtx.fillStyle = burstGradient;
trailsCtx.fillRect(bf.x - bf.radius, bf.y - bf.radius, bf.radius * 2, bf.radius * 2);
BurstFlash.returnInstance(bf);
}
// Draw stars
trailsCtx.lineWidth = Star.drawWidth;
trailsCtx.lineCap = 'round';
mainCtx.strokeStyle = '#fff';mainCtx.lineWidth = 1;
mainCtx.beginPath();
COLOR_CODES.forEach(color => {
const stars = Star.active[color];
trailsCtx.strokeStyle = color;
trailsCtx.beginPath();
stars.forEach(star => {
trailsCtx.moveTo(star.x, star.y);
trailsCtx.lineTo(star.prevX, star.prevY);
mainCtx.moveTo(star.x, star.y);
mainCtx.lineTo(star.x - star.speedX * 1.6, star.y - star.speedY * 1.6);
});
trailsCtx.stroke();
});
mainCtx.stroke();// Draw sparks
trailsCtx.lineWidth = Spark.drawWidth;
trailsCtx.lineCap = 'butt';
COLOR_CODES.forEach(color => {
const sparks = Spark.active[color];
trailsCtx.strokeStyle = color;
trailsCtx.beginPath();
sparks.forEach(spark => {
trailsCtx.moveTo(spark.x, spark.y);
trailsCtx.lineTo(spark.prevX, spark.prevY);
});
trailsCtx.stroke();
});
// Render speed bar if visible
if (speedBarOpacity) {
const speedBarHeight = 6;
mainCtx.globalAlpha = speedBarOpacity;
mainCtx.fillStyle = COLOR.Blue;
mainCtx.fillRect(0, height - speedBarHeight, width * simSpeed, speedBarHeight);
mainCtx.globalAlpha = 1;
}
trailsCtx.resetTransform();
mainCtx.resetTransform();
}// Draw colored overlay based on combined brightness of stars (light up the sky!)
// Note: this is applied to the canvas container's background-color, so it's behind the particles
const currentSkyColor = { r: 0, g: 0, b: 0 };
const targetSkyColor = { r: 0, g: 0, b: 0 };
function colorSky(speed) {
// The maximum r, g, or b value that will be used (255 would represent no maximum)
const maxSkySaturation = 30;
// How many stars are required in total to reach maximum sky brightness
const maxStarCount = 500;
let totalStarCount = 0;
// Initialize sky as black
targetSkyColor.r = 0;
targetSkyColor.g = 0;
targetSkyColor.b = 0;
// Add each known color to sky, multiplied by particle count of that color. This will put RGB values wildly out of bounds, but we'll scale them back later.
// Also add up total star count.
COLOR_CODES.forEach(color => {
const tuple = COLOR_TUPLES[color];
const count =  Star.active[color].length;
totalStarCount += count;
targetSkyColor.r += tuple.r * count;
targetSkyColor.g += tuple.g * count;
targetSkyColor.b += tuple.b * count;
});
// Clamp intensity at 1.0, and map to a custom non-linear curve. This allows few stars to perceivably light up the sky, while more stars continue to increase the brightness but at a lesser rate. This is more inline with humans' non-linear brightness perception.
const intensity = Math.pow(Math.min(1, totalStarCount / maxStarCount), 0.3);
// Figure out which color component has the highest value, so we can scale them without affecting the ratios.
// Prevent 0 from being used, so we don't divide by zero in the next step.
const maxColorComponent = Math.max(1, targetSkyColor.r, targetSkyColor.g, targetSkyColor.b);
// Scale all color components to a max of `maxSkySaturation`, and apply intensity.
targetSkyColor.r = targetSkyColor.r / maxColorComponent * maxSkySaturation * intensity;
targetSkyColor.g = targetSkyColor.g / maxColorComponent * maxSkySaturation * intensity;
targetSkyColor.b = targetSkyColor.b / maxColorComponent * maxSkySaturation * intensity;
// Animate changes to color to smooth out transitions.
const colorChange = 10;
currentSkyColor.r += (targetSkyColor.r - currentSkyColor.r) / colorChange * speed;
currentSkyColor.g += (targetSkyColor.g - currentSkyColor.g) / colorChange * speed;
currentSkyColor.b += (targetSkyColor.b - currentSkyColor.b) / colorChange * speed;
appNodes.canvasContainer.style.backgroundColor = `rgb(${currentSkyColor.r | 0}, ${currentSkyColor.g | 0}, ${currentSkyColor.b | 0})`;
}mainStage.addEventListener('ticker', update);// Helper used to semi-randomly spread particles over an arc
// Values are flexible - `start` and `arcLength` can be negative, and `randomness` is simply a multiplier for random addition.
function createParticleArc(start, arcLength, count, randomness, particleFactory) {
const angleDelta = arcLength / count;
// Sometimes there is an extra particle at the end, too close to the start. Subtracting half the angleDelta ensures that is skipped.
// Would be nice to fix this a better way.
const end = start + arcLength - (angleDelta * 0.5);
if (end > start) {
// Optimization: `angle=angle+angleDelta` vs. angle+=angleDelta
// V8 deoptimises with let compound assignment
for (let angle=start; angle<end; angle=angle+angleDelta) {
particleFactory(angle + Math.random() * angleDelta * randomness);
}
}
else {
for (let angle=start; angle>end; angle=angle+angleDelta) {
particleFactory(angle + Math.random() * angleDelta * randomness);
}
}
}// Various star effects.
// These are designed to be attached to a star's `onDeath` event.// Crossette breaks star into four same-color pieces which branch in a cross-like shape.
function crossetteEffect(star) {
const startAngle = Math.random() * PI_HALF;
createParticleArc(startAngle, PI_2, 4, 0.5, (angle) => {
Star.add(
star.x,
star.y,
star.color,
angle,
Math.random() * 0.6 + 0.75,
600
);
});
}// Flower is like a mini shell
function floralEffect(star) {
const startAngle = Math.random() * PI_HALF;
createParticleArc(startAngle, PI_2, 24, 1, (angle) => {
Star.add(
star.x,
star.y,
star.color,
angle,
// apply near cubic falloff to speed (places more particles towards outside)
Math.pow(Math.random(), 0.45) * 2.4,
1000 + Math.random() * 300,
star.speedX,
star.speedY
);
});
// Queue burst flash render
BurstFlash.add(star.x, star.y, 24);
}// Floral burst with willow stars
function fallingLeavesEffect(star) {
const startAngle = Math.random() * PI_HALF;
createParticleArc(startAngle, PI_2, 12, 1, (angle) => {
const newStar = Star.add(
star.x,
star.y,
INVISIBLE,
angle,
// apply near cubic falloff to speed (places more particles towards outside)
Math.pow(Math.random(), 0.45) * 2.4,
2400 + Math.random() * 600,
star.speedX,
star.speedY
);
newStar.sparkColor = COLOR.Gold;
newStar.sparkFreq = 72;
newStar.sparkSpeed = 0.28;
newStar.sparkLife = 750;
newStar.sparkLifeVariation = 3.2;
});
// Queue burst flash render
BurstFlash.add(star.x, star.y, 24);
}// Crackle pops into a small cloud of golden sparks.
function crackleEffect(star) {
createParticleArc(0, PI_2, 10, 1.8, (angle) => {
Spark.add(
star.x,
star.y,
COLOR.Gold,
angle,
// apply near cubic falloff to speed (places more particles towards outside)
Math.pow(Math.random(), 0.45) * 2.4,
300 + Math.random() * 200
);
});
}/*** Shell can be constructed with options:** size:      Size of the burst.* starCount: Number of stars to create. This is optional, and will be set to a reasonable quantity for size if omitted.* starLife:* starLifeVariation:* color:* glitterColor:* glitter: One of: 'light', 'medium', 'heavy', 'streamer', 'willow'* pistil:* pistilColor:* streamers:* crossette:* floral:* crackle:*/class Shell {
constructor(options) {
Object.assign(this, options);
this.starLifeVariation = options.starLifeVariation || 0.125;
this.color = options.color || randomColor();
this.glitterColor = options.glitterColor || this.color;
// Set default starCount if needed, will be based on shell size and scale exponentially, like a sphere's surface area.
if (!this.starCount) {
const density = options.starDensity || 1;
const scaledSize = this.size / 50 * density;
this.starCount = scaledSize * scaledSize;
}
}
launch(position, launchHeight) {
const { width, height } = mainStage;
// Distance from sides of screen to keep shells.
const hpad = 60;
// Distance from top of screen to keep shell bursts.
const vpad = 50;
// Minimum burst height, as a percentage of stage height
const minHeightPercent = 0.45;
// Minimum burst height in px
const minHeight = height - height * minHeightPercent;
const launchX = position * (width - hpad * 2) + hpad;
const launchY = height;
const burstY = minHeight - (launchHeight * (minHeight - vpad));
const launchDistance = launchY - burstY;
// Using a custom power curve to approximate Vi needed to reach launchDistance under gravity and air drag.
// Magic numbers came from testing.
const launchVelocity = Math.pow(launchDistance * 0.04, 0.64);
const comet = this.comet = Star.add(
launchX,
launchY,
typeof this.color === 'string' && this.color !== 'random' ? this.color : COLOR.White,
Math.PI,
launchVelocity * (this.horsetail ? 1.2 : 1),
// Hang time is derived linearly from Vi; exact number came from testing
launchVelocity * (this.horsetail ? 100 : 400)
);
// making comet "heavy" limits air drag
comet.heavy = true;
// comet spark trail
comet.spinRadius = 0.78;
comet.sparkFreq = 16;
if (this.glitter === 'willow' || this.fallingLeaves) {
comet.sparkFreq = 10;
comet.sparkSpeed = 0.5;
comet.sparkLife = 500;
comet.sparkLifeVariation = 3;
}
if (this.color === INVISIBLE) {
comet.sparkColor = COLOR.Gold;
}
comet.onDeath = comet => this.burst(comet.x, comet.y);
// comet.onDeath = () => this.burst(launchX, burstY);
}
burst(x, y) {
// Set burst speed so overall burst grows to set size. This specific formula was derived from testing, and is affected by simulated air drag.
const speed = this.size / 96;let color, onDeath, sparkFreq, sparkSpeed, sparkLife;
let sparkLifeVariation = 0.25;
if (this.crossette) onDeath = crossetteEffect;
if (this.floral) onDeath = floralEffect;
if (this.crackle) onDeath = crackleEffect;
if (this.fallingLeaves) onDeath = fallingLeavesEffect;
if (this.glitter === 'light') {
sparkFreq = 200;
sparkSpeed = 0.25;
sparkLife = 600;
}
else if (this.glitter === 'medium') {
sparkFreq = 100;
sparkSpeed = 0.36;
sparkLife = 1400;
}
else if (this.glitter === 'heavy') {
sparkFreq = 42;
sparkSpeed = 0.62;
sparkLife = 2800;
}
else if (this.glitter === 'streamer') {
sparkFreq = 20;
sparkSpeed = 0.75;
sparkLife = 800;
}
else if (this.glitter === 'willow') {
sparkFreq = 72;
sparkSpeed = 0.28;
sparkLife = 1000;
sparkLifeVariation = 3.4;
}
const starFactory = angle => {
const star = Star.add(
x,
y,
color || randomColor(),
angle,
// apply near cubic falloff to speed (places more particles towards outside)
Math.pow(Math.random(), 0.45) * speed,
// add minor variation to star life
this.starLife + Math.random() * this.starLife * this.starLifeVariation,
this.horsetail && this.comet && this.comet.speedX,
this.horsetail && this.comet && this.comet.speedY
);star.onDeath = onDeath;if (this.glitter) {
star.sparkFreq = sparkFreq;
star.sparkSpeed = sparkSpeed;
star.sparkLife = sparkLife;
star.sparkLifeVariation = sparkLifeVariation;
star.sparkColor = this.glitterColor;
star.sparkTimer = Math.random() * star.sparkFreq;
}
};
if (typeof this.color === 'string') {
if (this.color === 'random') {
color = null; // falsey value creates random color in starFactory
} else {
color = this.color;
}
// Rings have positional randomness, but are rotated randomly
if (this.ring) {
const ringStartAngle = Math.random() * Math.PI;
const ringSquash = Math.pow(Math.random(), 0.45) * 0.992 + 0.008;
createParticleArc(0, PI_2, this.starCount, 0, angle => {
// Create a ring, squashed horizontally
const initSpeedX = Math.sin(angle) * speed * ringSquash;
const initSpeedY = Math.cos(angle) * speed;
// Rotate ring
const newSpeed = MyMath.pointDist(0, 0, initSpeedX, initSpeedY);
const newAngle = MyMath.pointAngle(0, 0, initSpeedX, initSpeedY) + ringStartAngle;
const star = Star.add(
x,
y,
color,
newAngle,
// apply near cubic falloff to speed (places more particles towards outside)
newSpeed,//speed,
// add minor variation to star life
this.starLife + Math.random() * this.starLife * this.starLifeVariation
);
if (this.glitter) {
star.sparkFreq = sparkFreq;
star.sparkSpeed = sparkSpeed;
star.sparkLife = sparkLife;
star.sparkLifeVariation = sparkLifeVariation;
star.sparkColor = this.glitterColor;
star.sparkTimer = Math.random() * star.sparkFreq;
}
});
}
// "Normal burst
else {
createParticleArc(0, PI_2, this.starCount, 1, starFactory);
}
}
else if (Array.isArray(this.color)) {
let start, start2, arc;
if (Math.random() < 0.5) {
start = Math.random() * Math.PI;
start2 = start + Math.PI;
arc = Math.PI;
} else {
start = 0;
start2 = 0;
arc = PI_2;
}
color = this.color[0];
createParticleArc(start, arc, this.starCount/2, 1, starFactory);
color = this.color[1];
createParticleArc(start2, arc, this.starCount/2, 1, starFactory)
}
if (this.pistil) {
const innerShell = new Shell({
size: this.size * 0.5,
starLife: this.starLife * 0.7,
starLifeVariation: this.starLifeVariation,
starDensity: 1.65,
color: this.pistilColor,
glitter: 'light',
glitterColor: this.pistilColor === COLOR.Gold ? COLOR.Gold : COLOR.White
});
innerShell.burst(x, y);
}
if (this.streamers) {
const innerShell = new Shell({
size: this.size,
starLife: this.starLife * 0.8,
starLifeVariation: this.starLifeVariation,
starCount: Math.max(6, this.size / 45) | 0,
color: COLOR.White,
glitter: 'streamer'
});
innerShell.burst(x, y);
}
// Queue burst flash render
BurstFlash.add(x, y, this.size / 8);
}
}const BurstFlash = {
active: [],
_pool: [],
_new() {
return {}
},
add(x, y, radius) {
const instance = this._pool.pop() || this._new();
instance.x = x;
instance.y = y;
instance.radius = radius;
this.active.push(instance);
return instance;
},
returnInstance(instance) {
this._pool.push(instance);
}
};// Helper to generate objects for storing active particles.
// Particles are stored in arrays keyed by color (code, not name) for improved rendering performance.
function createParticleCollection() {
const collection = {};
COLOR_CODES_W_INVIS.forEach(color => {
collection[color] = [];
});
return collection;
}const Star = {
// Visual properties
drawWidth: 3,
airDrag: 0.98,
airDragHeavy: 0.992,
// Star particles will be keyed by color
active: createParticleCollection(),
_pool: [],
_new() {
return {};
},add(x, y, color, angle, speed, life, speedOffX, speedOffY) {
const instance = this._pool.pop() || this._new();
instance.heavy = false;
instance.x = x;
instance.y = y;
instance.prevX = x;
instance.prevY = y;
instance.color = color;
instance.speedX = Math.sin(angle) * speed + (speedOffX || 0);
instance.speedY = Math.cos(angle) * speed + (speedOffY || 0);
instance.life = life;
instance.spinAngle = Math.random() * PI_2;
instance.spinSpeed = 0.8;
instance.spinRadius = 0;
instance.sparkFreq = 0; // ms between spark emissions
instance.sparkSpeed = 1;
instance.sparkTimer = 0;
instance.sparkColor = color;
instance.sparkLife = 750;
instance.sparkLifeVariation = 0.25;
this.active[color].push(instance);
return instance;
},// Public method for cleaning up and returning an instance back to the pool.
returnInstance(instance) {
// Call onDeath handler if available (and pass it current star instance)
instance.onDeath && instance.onDeath(instance);
// Clean up
instance.onDeath = null;
// Add back to the pool.
this._pool.push(instance);
}
};const Spark = {
// Visual properties
drawWidth: 0.75,
airDrag: 0.9,
// Star particles will be keyed by color
active: createParticleCollection(),
_pool: [],
_new() {
return {};
},add(x, y, color, angle, speed, life) {
const instance = this._pool.pop() || this._new();
instance.x = x;
instance.y = y;
instance.prevX = x;
instance.prevY = y;
instance.color = color;
instance.speedX = Math.sin(angle) * speed;
instance.speedY = Math.cos(angle) * speed;
instance.life = life;
this.active[color].push(instance);
return instance;
},// Public method for cleaning up and returning an instance back to the pool.
returnInstance(instance) {
// Add back to the pool.
this._pool.push(instance);
}
};function init() {
// Populate dropdowns
// shell type
let options = '';
shellNames.forEach(opt => options += options += '<option value="' + opt + '">' + opt + '</option>');
appNodes.shellType.innerHTML = options;
// shell size
options = '';
['3"', '5"', '6"', '8"', '12"'].forEach((opt, i) => options += '<option value="' + opt + '">' + opt + '</option>');
appNodes.shellSize.innerHTML = options;renderApp(store.state);
}</script></body>
</html>

灿烂烟花

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head><title> 真实烟花</title><style>body {padding: 0;}canvas {display: block;}</style>
</head><body><canvas id="canvas"></canvas><script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script><script>$(function () {var canvas = $('#canvas')[0];canvas.width = $(window).width();canvas.height = $(window).height();var ctx = canvas.getContext('2d');// resize$(window).on('resize', function () {canvas.width = $(window).width();canvas.height = $(window).height();ctx.fillStyle = '#000';ctx.fillRect(0, 0, canvas.width, canvas.height);});// initctx.fillStyle = '#000';ctx.fillRect(0, 0, canvas.width, canvas.height);// objectsvar listFire = [];var listFirework = [];var fireNumber = 10;var center = { x: canvas.width / 2, y: canvas.height / 2 };var range = 100;for (var i = 0; i < fireNumber; i++) {var fire = {x: Math.random() * range / 2 - range / 4 + center.x,y: Math.random() * range * 2 + canvas.height,size: Math.random() + 0.5,fill: '#fd1',vx: Math.random() - 0.5,vy: -(Math.random() + 4),ax: Math.random() * 0.02 - 0.01,far: Math.random() * range + (center.y - range)};fire.base = {x: fire.x,y: fire.y,vx: fire.vx};//listFire.push(fire);}function randColor() {var r = Math.floor(Math.random() * 256);var g = Math.floor(Math.random() * 256);var b = Math.floor(Math.random() * 256);var color = 'rgb($r, $g, $b)';color = color.replace('$r', r);color = color.replace('$g', g);color = color.replace('$b', b);return color;}(function loop() {requestAnimationFrame(loop);update();draw();})();function update() {for (var i = 0; i < listFire.length; i++) {var fire = listFire[i];//if (fire.y <= fire.far) {// case add fireworkvar color = randColor();for (var i = 0; i < fireNumber * 5; i++) {var firework = {x: fire.x,y: fire.y,size: Math.random() + 1.5,fill: color,vx: Math.random() * 5 - 2.5,vy: Math.random() * -5 + 1.5,ay: 0.05,alpha: 1,life: Math.round(Math.random() * range / 2) + range / 2};firework.base = {life: firework.life,size: firework.size};listFirework.push(firework);}// resetfire.y = fire.base.y;fire.x = fire.base.x;fire.vx = fire.base.vx;fire.ax = Math.random() * 0.02 - 0.01;}//fire.x += fire.vx;fire.y += fire.vy;fire.vx += fire.ax;}for (var i = listFirework.length - 1; i >= 0; i--) {var firework = listFirework[i];if (firework) {firework.x += firework.vx;firework.y += firework.vy;firework.vy += firework.ay;firework.alpha = firework.life / firework.base.life;firework.size = firework.alpha * firework.base.size;firework.alpha = firework.alpha > 0.6 ? 1 : firework.alpha;//firework.life--;if (firework.life <= 0) {listFirework.splice(i, 1);}}}}function draw() {// clearctx.globalCompositeOperation = 'source-over';ctx.globalAlpha = 0.18;ctx.fillStyle = '#000';ctx.fillRect(0, 0, canvas.width, canvas.height);// re-drawctx.globalCompositeOperation = 'screen';ctx.globalAlpha = 1;for (var i = 0; i < listFire.length; i++) {var fire = listFire[i];ctx.beginPath();ctx.arc(fire.x, fire.y, fire.size, 0, Math.PI * 2);ctx.closePath();ctx.fillStyle = fire.fill;ctx.fill();}for (var i = 0; i < listFirework.length; i++) {var firework = listFirework[i];ctx.globalAlpha = firework.alpha;ctx.beginPath();ctx.arc(firework.x, firework.y, firework.size, 0, Math.PI * 2);ctx.closePath();ctx.fillStyle = firework.fill;ctx.fill();}}})</script>
</body></html>

 炫酷漩涡

<!doctype html><html><head><meta charset="utf-8"><title>H5,200行代码实现粒子漩涡特效</title><style>html,body{margin:0px;width:100%;height:100%;overflow:hidden;background: #000000;}#canvas{position:absolute;width:100%;height:100%;}</style></head><body><canvas id="canvas"></canvas><script>function project3D(x,y,z,vars){var p,d;x-=vars.camX;y-=vars.camY-8;z-=vars.camZ;p=Math.atan2(x,z);d=Math.sqrt(x*x+z*z);x=Math.sin(p-vars.yaw)*d;z=Math.cos(p-vars.yaw)*d;p=Math.atan2(y,z);d=Math.sqrt(y*y+z*z);y=Math.sin(p-vars.pitch)*d;z=Math.cos(p-vars.pitch)*d;var rx1=-1000;var ry1=1;var rx2=1000;var ry2=1;var rx3=0;var ry3=0;var rx4=x;var ry4=z;var uc=(ry4-ry3)*(rx2-rx1)-(rx4-rx3)*(ry2-ry1);var ua=((rx4-rx3)*(ry1-ry3)-(ry4-ry3)*(rx1-rx3))/uc;var ub=((rx2-rx1)*(ry1-ry3)-(ry2-ry1)*(rx1-rx3))/uc;if(!z)z=0.000000001;if(ua>0&&ua<1&&ub>0&&ub<1){return {x:vars.cx+(rx1+ua*(rx2-rx1))*vars.scale,y:vars.cy+y/z*vars.scale,d:(x*x+y*y+z*z)};}else{return { d:-1 };}}function elevation(x,y,z){var dist = Math.sqrt(x*x+y*y+z*z);if(dist && z/dist>=-1 && z/dist <=1) return Math.acos(z / dist);return 0.00000001;}function rgb(col){col += 0.000001;var r = parseInt((0.5+Math.sin(col)*0.5)*16);var g = parseInt((0.5+Math.cos(col)*0.5)*16);var b = parseInt((0.5-Math.sin(col)*0.5)*16);return "#"+r.toString(16)+g.toString(16)+b.toString(16);}function interpolateColors(RGB1,RGB2,degree){var w2=degree;var w1=1-w2;return [w1*RGB1[0]+w2*RGB2[0],w1*RGB1[1]+w2*RGB2[1],w1*RGB1[2]+w2*RGB2[2]];}function rgbArray(col){col += 0.000001;var r = parseInt((0.5+Math.sin(col)*0.5)*256);var g = parseInt((0.5+Math.cos(col)*0.5)*256);var b = parseInt((0.5-Math.sin(col)*0.5)*256);return [r, g, b];}function colorString(arr){var r = parseInt(arr[0]);var g = parseInt(arr[1]);var b = parseInt(arr[2]);return "#"+("0" + r.toString(16) ).slice (-2)+("0" + g.toString(16) ).slice (-2)+("0" + b.toString(16) ).slice (-2);}function process(vars){if(vars.points.length<vars.initParticles) for(var i=0;i<5;++i) spawnParticle(vars);var p,d,t;p = Math.atan2(vars.camX, vars.camZ);d = Math.sqrt(vars.camX * vars.camX + vars.camZ * vars.camZ);d -= Math.sin(vars.frameNo / 80) / 25;t = Math.cos(vars.frameNo / 300) / 165;vars.camX = Math.sin(p + t) * d;vars.camZ = Math.cos(p + t) * d;vars.camY = -Math.sin(vars.frameNo / 220) * 15;vars.yaw = Math.PI + p + t;vars.pitch = elevation(vars.camX, vars.camZ, vars.camY) - Math.PI / 2;var t;for(var i=0;i<vars.points.length;++i){x=vars.points[i].x;y=vars.points[i].y;z=vars.points[i].z;d=Math.sqrt(x*x+z*z)/1.0075;t=.1/(1+d*d/5);p=Math.atan2(x,z)+t;vars.points[i].x=Math.sin(p)*d;vars.points[i].z=Math.cos(p)*d;vars.points[i].y+=vars.points[i].vy*t*((Math.sqrt(vars.distributionRadius)-d)*2);if(vars.points[i].y>vars.vortexHeight/2 || d<.25){vars.points.splice(i,1);spawnParticle(vars);}}}function drawFloor(vars){var x,y,z,d,point,a;for (var i = -25; i <= 25; i += 1) {for (var j = -25; j <= 25; j += 1) {x = i*2;z = j*2;y = vars.floor;d = Math.sqrt(x * x + z * z);point = project3D(x, y-d*d/85, z, vars);if (point.d != -1) {size = 1 + 15000 / (1 + point.d);a = 0.15 - Math.pow(d / 50, 4) * 0.15;if (a > 0) {vars.ctx.fillStyle = colorString(interpolateColors(rgbArray(d/26-vars.frameNo/40),[0,128,32],.5+Math.sin(d/6-vars.frameNo/8)/2));vars.ctx.globalAlpha = a;vars.ctx.fillRect(point.x-size/2,point.y-size/2,size,size);}}}}vars.ctx.fillStyle = "#82f";for (var i = -25; i <= 25; i += 1) {for (var j = -25; j <= 25; j += 1) {x = i*2;z = j*2;y = -vars.floor;d = Math.sqrt(x * x + z * z);point = project3D(x, y+d*d/85, z, vars);if (point.d != -1) {size = 1 + 15000 / (1 + point.d);a = 0.15 - Math.pow(d / 50, 4) * 0.15;if (a > 0) {vars.ctx.fillStyle = colorString(interpolateColors(rgbArray(-d/26-vars.frameNo/40),[32,0,128],.5+Math.sin(-d/6-vars.frameNo/8)/2));vars.ctx.globalAlpha = a;vars.ctx.fillRect(point.x-size/2,point.y-size/2,size,size);}}}}}function sortFunction(a,b){return b.dist-a.dist;}function draw(vars){vars.ctx.globalAlpha=.15;vars.ctx.fillStyle="#000";vars.ctx.fillRect(0, 0, canvas.width, canvas.height);drawFloor(vars);var point,x,y,z,a;for(var i=0;i<vars.points.length;++i){x=vars.points[i].x;y=vars.points[i].y;z=vars.points[i].z;point=project3D(x,y,z,vars);if(point.d != -1){vars.points[i].dist=point.d;size=1+vars.points[i].radius/(1+point.d);d=Math.abs(vars.points[i].y);a = .8 - Math.pow(d / (vars.vortexHeight/2), 1000) * .8;vars.ctx.globalAlpha=a>=0&&a<=1?a:0;vars.ctx.fillStyle=rgb(vars.points[i].color);if(point.x>-1&&point.x<vars.canvas.width&&point.y>-1&&point.y<vars.canvas.height)vars.ctx.fillRect(point.x-size/2,point.y-size/2,size,size);}}vars.points.sort(sortFunction);}function spawnParticle(vars){var p,ls;pt={};p=Math.PI*2*Math.random();ls=Math.sqrt(Math.random()*vars.distributionRadius);pt.x=Math.sin(p)*ls;pt.y=-vars.vortexHeight/2;pt.vy=vars.initV/20+Math.random()*vars.initV;pt.z=Math.cos(p)*ls;pt.radius=200+800*Math.random();pt.color=pt.radius/1000+vars.frameNo/250;vars.points.push(pt);}function frame(vars) {if(vars === undefined){var vars={};vars.canvas = document.querySelector("canvas");vars.ctx = vars.canvas.getContext("2d");vars.canvas.width = document.body.clientWidth;vars.canvas.height = document.body.clientHeight;window.addEventListener("resize", function(){vars.canvas.width = document.body.clientWidth;vars.canvas.height = document.body.clientHeight;vars.cx=vars.canvas.width/2;vars.cy=vars.canvas.height/2;}, true);vars.frameNo=0;vars.camX = 0;vars.camY = 0;vars.camZ = -14;vars.pitch = elevation(vars.camX, vars.camZ, vars.camY) - Math.PI / 2;vars.yaw = 0;vars.cx=vars.canvas.width/2;vars.cy=vars.canvas.height/2;vars.bounding=10;vars.scale=500;vars.floor=26.5;vars.points=[];vars.initParticles=700;vars.initV=.01;vars.distributionRadius=800;vars.vortexHeight=25;}vars.frameNo++;requestAnimationFrame(function() {frame(vars);});process(vars);draw(vars);}frame();</script></body></html>

3D立体烟花特效

<!doctype html><html><head><meta charset="utf-8"><title>烟花特效</title><style>html,body{margin:0px;width:100%;height:100%;overflow:hidden;background:#000;}#canvas{width:100%;height:100%;}</style></head><body><canvas id="canvas"></canvas><script>function initVars(){pi=Math.PI;ctx=canvas.getContext("2d");canvas.width=canvas.clientWidth;canvas.height=canvas.clientHeight;cx=canvas.width/2;cy=canvas.height/2;playerZ=-25;playerX=playerY=playerVX=playerVY=playerVZ=pitch=yaw=pitchV=yawV=0;scale=600;seedTimer=0;seedInterval=5,seedLife=100;gravity=.02;seeds=new Array();sparkPics=new Array();s="https://cantelope.org/NYE/";for(i=1;i<=10;++i){sparkPic=new Image();sparkPic.src=s+"spark"+i+".png";sparkPics.push(sparkPic);}sparks=new Array();pow1=new Audio(s+"pow1.ogg");pow2=new Audio(s+"pow2.ogg");pow3=new Audio(s+"pow3.ogg");pow4=new Audio(s+"pow4.ogg");frames = 0;}function rasterizePoint(x,y,z){var p,d;x-=playerX;y-=playerY;z-=playerZ;p=Math.atan2(x,z);d=Math.sqrt(x*x+z*z);x=Math.sin(p-yaw)*d;z=Math.cos(p-yaw)*d;p=Math.atan2(y,z);d=Math.sqrt(y*y+z*z);y=Math.sin(p-pitch)*d;z=Math.cos(p-pitch)*d;var rx1=-1000,ry1=1,rx2=1000,ry2=1,rx3=0,ry3=0,rx4=x,ry4=z,uc=(ry4-ry3)*(rx2-rx1)-(rx4-rx3)*(ry2-ry1);if(!uc) return {x:0,y:0,d:-1};var ua=((rx4-rx3)*(ry1-ry3)-(ry4-ry3)*(rx1-rx3))/uc;var ub=((rx2-rx1)*(ry1-ry3)-(ry2-ry1)*(rx1-rx3))/uc;if(!z)z=.000000001;if(ua>0&&ua<1&&ub>0&&ub<1){return {x:cx+(rx1+ua*(rx2-rx1))*scale,y:cy+y/z*scale,d:Math.sqrt(x*x+y*y+z*z)};}else{return {x:cx+(rx1+ua*(rx2-rx1))*scale,y:cy+y/z*scale,d:-1};}}function spawnSeed(){seed=new Object();seed.x=-50+Math.random()*100;seed.y=25;seed.z=-50+Math.random()*100;seed.vx=.1-Math.random()*.2;seed.vy=-1.5;//*(1+Math.random()/2);seed.vz=.1-Math.random()*.2;seed.born=frames;seeds.push(seed);}function splode(x,y,z){t=5+parseInt(Math.random()*150);sparkV=1+Math.random()*2.5;type=parseInt(Math.random()*3);switch(type){case 0:pic1=parseInt(Math.random()*10);break;case 1:pic1=parseInt(Math.random()*10);do{ pic2=parseInt(Math.random()*10); }while(pic2==pic1);break;case 2:pic1=parseInt(Math.random()*10);do{ pic2=parseInt(Math.random()*10); }while(pic2==pic1);do{ pic3=parseInt(Math.random()*10); }while(pic3==pic1 || pic3==pic2);break;}for(m=1;m<t;++m){spark=new Object();spark.x=x; spark.y=y; spark.z=z;p1=pi*2*Math.random();p2=pi*Math.random();v=sparkV*(1+Math.random()/6)spark.vx=Math.sin(p1)*Math.sin(p2)*v;spark.vz=Math.cos(p1)*Math.sin(p2)*v;spark.vy=Math.cos(p2)*v;switch(type){case 0: spark.img=sparkPics[pic1]; break;case 1:spark.img=sparkPics[parseInt(Math.random()*2)?pic1:pic2];break;case 2:switch(parseInt(Math.random()*3)){case 0: spark.img=sparkPics[pic1]; break;case 1: spark.img=sparkPics[pic2]; break;case 2: spark.img=sparkPics[pic3]; break;}break;}spark.radius=25+Math.random()*50;spark.alpha=1;spark.trail=new Array();sparks.push(spark);}switch(parseInt(Math.random()*4)){case 0:    pow=new Audio(s+"pow1.ogg"); break;case 1:    pow=new Audio(s+"pow2.ogg"); break;case 2:    pow=new Audio(s+"pow3.ogg"); break;case 3:    pow=new Audio(s+"pow4.ogg"); break;}d=Math.sqrt((x-playerX)*(x-playerX)+(y-playerY)*(y-playerY)+(z-playerZ)*(z-playerZ));pow.volume=1.5/(1+d/10);}function doLogic(){if(seedTimer<frames){seedTimer=frames+seedInterval*Math.random()*10;spawnSeed();}for(i=0;i<seeds.length;++i){seeds[i].vy+=gravity;seeds[i].x+=seeds[i].vx;seeds[i].y+=seeds[i].vy;seeds[i].z+=seeds[i].vz;if(frames-seeds[i].born>seedLife){splode(seeds[i].x,seeds[i].y,seeds[i].z);seeds.splice(i,1);}}for(i=0;i<sparks.length;++i){if(sparks[i].alpha>0 && sparks[i].radius>5){sparks[i].alpha-=.01;sparks[i].radius/=1.02;sparks[i].vy+=gravity;point=new Object();point.x=sparks[i].x;point.y=sparks[i].y;point.z=sparks[i].z;if(sparks[i].trail.length){x=sparks[i].trail[sparks[i].trail.length-1].x;y=sparks[i].trail[sparks[i].trail.length-1].y;z=sparks[i].trail[sparks[i].trail.length-1].z;d=((point.x-x)*(point.x-x)+(point.y-y)*(point.y-y)+(point.z-z)*(point.z-z));if(d>9){sparks[i].trail.push(point);}}else{sparks[i].trail.push(point);}if(sparks[i].trail.length>5)sparks[i].trail.splice(0,1);sparks[i].x+=sparks[i].vx;sparks[i].y+=sparks[i].vy;sparks[i].z+=sparks[i].vz;sparks[i].vx/=1.075;sparks[i].vy/=1.075;sparks[i].vz/=1.075;}else{sparks.splice(i,1);}}p=Math.atan2(playerX,playerZ);d=Math.sqrt(playerX*playerX+playerZ*playerZ);d+=Math.sin(frames/80)/1.25;t=Math.sin(frames/200)/40;playerX=Math.sin(p+t)*d;playerZ=Math.cos(p+t)*d;yaw=pi+p+t;}function rgb(col){var r = parseInt((.5+Math.sin(col)*.5)*16);var g = parseInt((.5+Math.cos(col)*.5)*16);var b = parseInt((.5-Math.sin(col)*.5)*16);return "#"+r.toString(16)+g.toString(16)+b.toString(16);}function draw(){ctx.clearRect(0,0,cx*2,cy*2);ctx.fillStyle="#ff8";for(i=-100;i<100;i+=3){for(j=-100;j<100;j+=4){x=i;z=j;y=25;point=rasterizePoint(x,y,z);if(point.d!=-1){size=250/(1+point.d);d = Math.sqrt(x * x + z * z);a = 0.75 - Math.pow(d / 100, 6) * 0.75;if(a>0){ctx.globalAlpha = a;ctx.fillRect(point.x-size/2,point.y-size/2,size,size);}}}}ctx.globalAlpha=1;for(i=0;i<seeds.length;++i){point=rasterizePoint(seeds[i].x,seeds[i].y,seeds[i].z);if(point.d!=-1){size=200/(1+point.d);ctx.fillRect(point.x-size/2,point.y-size/2,size,size);}}point1=new Object();for(i=0;i<sparks.length;++i){point=rasterizePoint(sparks[i].x,sparks[i].y,sparks[i].z);if(point.d!=-1){size=sparks[i].radius*200/(1+point.d);if(sparks[i].alpha<0)sparks[i].alpha=0;if(sparks[i].trail.length){point1.x=point.x;point1.y=point.y;switch(sparks[i].img){case sparkPics[0]:ctx.strokeStyle="#f84";break;case sparkPics[1]:ctx.strokeStyle="#84f";break;case sparkPics[2]:ctx.strokeStyle="#8ff";break;case sparkPics[3]:ctx.strokeStyle="#fff";break;case sparkPics[4]:ctx.strokeStyle="#4f8";break;case sparkPics[5]:ctx.strokeStyle="#f44";break;case sparkPics[6]:ctx.strokeStyle="#f84";break;case sparkPics[7]:ctx.strokeStyle="#84f";break;case sparkPics[8]:ctx.strokeStyle="#fff";break;case sparkPics[9]:ctx.strokeStyle="#44f";break;}for(j=sparks[i].trail.length-1;j>=0;--j){point2=rasterizePoint(sparks[i].trail[j].x,sparks[i].trail[j].y,sparks[i].trail[j].z);if(point2.d!=-1){ctx.globalAlpha=j/sparks[i].trail.length*sparks[i].alpha/2;ctx.beginPath();ctx.moveTo(point1.x,point1.y);ctx.lineWidth=1+sparks[i].radius*10/(sparks[i].trail.length-j)/(1+point2.d);ctx.lineTo(point2.x,point2.y);ctx.stroke();point1.x=point2.x;point1.y=point2.y;}}}ctx.globalAlpha=sparks[i].alpha;}}}function frame(){if(frames>100000){seedTimer=0;frames=0;}frames++;draw();doLogic();requestAnimationFrame(frame);}window.addEventListener("resize",()=>{canvas.width=canvas.clientWidth;canvas.height=canvas.clientHeight;cx=canvas.width/2;cy=canvas.height/2;});initVars();frame();</script></body></html>

浪漫爱心

<html>
<head><meta charset="utf-8"><title>loveHeart</title><link rel="shortcut icon" href="http://zhouql.vip/images/心.png" type="image/x-icon"><style>html,body {height: 100%;padding: 0;margin: 0;background: #000;}canvas {position: absolute;width: 100%;height: 100%;}</style>
</head>
<body><canvas id="pinkboard"></canvas><script>var settings = {particles: {length: 500, duration: 2, velocity: 100, effect: -0.75, size: 32, },};(function () { var b = 0; var c = ["ms", "moz", "webkit", "o"]; for (var a = 0; a < c.length && !window.requestAnimationFrame; ++a) { window.requestAnimationFrame = window[c[a] + "RequestAnimationFrame"]; window.cancelAnimationFrame = window[c[a] + "CancelAnimationFrame"] || window[c[a] + "CancelRequestAnimationFrame"] } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function (h, e) { var d = new Date().getTime(); var f = Math.max(0, 16 - (d - b)); var g = window.setTimeout(function () { h(d + f) }, f); b = d + f; return g } } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function (d) { clearTimeout(d) } } }());var Point = (function () {function Point(x, y) {this.x = (typeof x !== 'undefined') ? x : 0;this.y = (typeof y !== 'undefined') ? y : 0;}Point.prototype.clone = function () {return new Point(this.x, this.y);};Point.prototype.length = function (length) {if (typeof length == 'undefined')return Math.sqrt(this.x * this.x + this.y * this.y);this.normalize();this.x *= length;this.y *= length;return this;};Point.prototype.normalize = function () {var length = this.length();this.x /= length;this.y /= length;return this;};return Point;})();var Particle = (function () {function Particle() {this.position = new Point();this.velocity = new Point();this.acceleration = new Point();this.age = 0;}Particle.prototype.initialize = function (x, y, dx, dy) {this.position.x = x;this.position.y = y;this.velocity.x = dx;this.velocity.y = dy;this.acceleration.x = dx * settings.particles.effect;this.acceleration.y = dy * settings.particles.effect;this.age = 0;};Particle.prototype.update = function (deltaTime) {this.position.x += this.velocity.x * deltaTime;this.position.y += this.velocity.y * deltaTime;this.velocity.x += this.acceleration.x * deltaTime;this.velocity.y += this.acceleration.y * deltaTime;this.age += deltaTime;};Particle.prototype.draw = function (context, image) {function ease(t) {return (--t) * t * t + 1;}var size = image.width * ease(this.age / settings.particles.duration);context.globalAlpha = 1 - this.age / settings.particles.duration;context.drawImage(image, this.position.x - size / 2, this.position.y - size / 2, size, size);};return Particle;})();var ParticlePool = (function () {var particles,firstActive = 0,firstFree = 0,duration = settings.particles.duration;function ParticlePool(length) {// create and populate particle poolparticles = new Array(length);for (var i = 0; i < particles.length; i++)particles[i] = new Particle();}ParticlePool.prototype.add = function (x, y, dx, dy) {particles[firstFree].initialize(x, y, dx, dy);// handle circular queuefirstFree++;if (firstFree == particles.length) firstFree = 0;if (firstActive == firstFree) firstActive++;if (firstActive == particles.length) firstActive = 0;};ParticlePool.prototype.update = function (deltaTime) {var i;// update active particlesif (firstActive < firstFree) {for (i = firstActive; i < firstFree; i++)particles[i].update(deltaTime);}if (firstFree < firstActive) {for (i = firstActive; i < particles.length; i++)particles[i].update(deltaTime);for (i = 0; i < firstFree; i++)particles[i].update(deltaTime);}// remove inactive particleswhile (particles[firstActive].age >= duration && firstActive != firstFree) {firstActive++;if (firstActive == particles.length) firstActive = 0;}};ParticlePool.prototype.draw = function (context, image) {// draw active particlesif (firstActive < firstFree) {for (i = firstActive; i < firstFree; i++)particles[i].draw(context, image);}if (firstFree < firstActive) {for (i = firstActive; i < particles.length; i++)particles[i].draw(context, image);for (i = 0; i < firstFree; i++)particles[i].draw(context, image);}};return ParticlePool;})();(function (canvas) {var context = canvas.getContext('2d'),particles = new ParticlePool(settings.particles.length),particleRate = settings.particles.length / settings.particles.duration, // particles/sectime;// get point on heart with -PI <= t <= PIfunction pointOnHeart(t) {return new Point(160 * Math.pow(Math.sin(t), 3),130 * Math.cos(t) - 50 * Math.cos(2 * t) - 20 * Math.cos(3 * t) - 10 * Math.cos(4 * t) + 25);}// creating the particle image using a dummy canvasvar image = (function () {var canvas = document.createElement('canvas'),context = canvas.getContext('2d');canvas.width = settings.particles.size;canvas.height = settings.particles.size;// helper function to create the pathfunction to(t) {var point = pointOnHeart(t);point.x = settings.particles.size / 2 + point.x * settings.particles.size / 350;point.y = settings.particles.size / 2 - point.y * settings.particles.size / 350;return point;}// create the pathcontext.beginPath();var t = -Math.PI;var point = to(t);context.moveTo(point.x, point.y);while (t < Math.PI) {t += 0.01; // baby steps!point = to(t);context.lineTo(point.x, point.y);}context.closePath();// create the fillcontext.fillStyle = '#ea80b0';context.fill();// create the imagevar image = new Image();image.src = canvas.toDataURL();return image;})();// render that thing!function render() {// next animation framerequestAnimationFrame(render);// update timevar newTime = new Date().getTime() / 1000,deltaTime = newTime - (time || newTime);time = newTime;// clear canvascontext.clearRect(0, 0, canvas.width, canvas.height);// create new particlesvar amount = particleRate * deltaTime;for (var i = 0; i < amount; i++) {var pos = pointOnHeart(Math.PI - 2 * Math.PI * Math.random());var dir = pos.clone().length(settings.particles.velocity);particles.add(canvas.width / 2 + pos.x, canvas.height / 2 - pos.y, dir.x, -dir.y);}// update and draw particlesparticles.update(deltaTime);particles.draw(context, image);}// handle (re-)sizing of the canvasfunction onResize() {canvas.width = canvas.clientWidth;canvas.height = canvas.clientHeight;}window.onresize = onResize;// delay rendering bootstrapsetTimeout(function () {onResize();render();}, 10);})(document.getElementById('pinkboard'));</script>
</body></html>

流星雨

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>流星雨</title><script>var context;var arr = new Array();var starCount = 800;var rains = new Array();var rainCount = 20;function init() {var stars = document.getElementById("stars");windowWidth = window.innerWidth; //当前的窗口的高度stars.width = windowWidth;stars.height = window.innerHeight;context = stars.getContext("2d");}//创建一个星星对象var Star = function () {this.x = windowWidth * Math.random();//横坐标this.y = 5000 * Math.random();//纵坐标this.text = ".";//文本this.color = "white";//颜色this.getColor = function () {var _r = Math.random();if (_r < 0.5) {this.color = "#333";} else {this.color = "white";}}
//初始化this.init = function () {this.getColor();}
//绘制this.draw = function () {context.fillStyle = this.color;context.fillText(this.text, this.x, this.y);}}//画月亮function drawMoon() {var moon = new Image();moon.src = "./images/moon.jpg"context.drawImage(moon, -5, -10);}//页面加载的时候window.onload = function () {init();
//画星星for (var i = 0; i < starCount; i++) {var star = new Star();star.init();star.draw();arr.push(star);}
//画流星for (var i = 0; i < rainCount; i++) {var rain = new MeteorRain();rain.init();rain.draw();rains.push(rain);}drawMoon();//绘制月亮playStars();//绘制闪动的星星playRains();//绘制流星}//星星闪起来function playStars() {for (var n = 0; n < starCount; n++) {arr[n].getColor();arr[n].draw();}setTimeout("playStars()", 100);}/*流星雨开始*/var MeteorRain = function () {this.x = -1;this.y = -1;this.length = -1;//长度this.angle = 30; //倾斜角度this.width = -1;//宽度this.height = -1;//高度this.speed = 1;//速度this.offset_x = -1;//横轴移动偏移量this.offset_y = -1;//纵轴移动偏移量this.alpha = 1; //透明度this.color1 = "";//流星的色彩this.color2 = ""; //流星的色彩/****************初始化函数********************/this.init = function () //初始化{this.getPos();this.alpha = 1;//透明度this.getRandomColor();
//最小长度,最大长度var x = Math.random() * 80 + 150;this.length = Math.ceil(x);
// x = Math.random()*10+30;this.angle = 30; //流星倾斜角x = Math.random() + 0.5;this.speed = Math.ceil(x); //流星的速度var cos = Math.cos(this.angle * 3.14 / 180);var sin = Math.sin(this.angle * 3.14 / 180);this.width = this.length * cos; //流星所占宽度this.height = this.length * sin;//流星所占高度this.offset_x = this.speed * cos;this.offset_y = this.speed * sin;}/**************获取随机颜色函数*****************/this.getRandomColor = function () {var a = Math.ceil(255 - 240 * Math.random());
//中段颜色this.color1 = "rgba(" + a + "," + a + "," + a + ",1)";
//结束颜色this.color2 = "black";}/***************重新计算流星坐标的函数******************/this.countPos = function ()//{
//往左下移动,x减少,y增加this.x = this.x - this.offset_x;this.y = this.y + this.offset_y;}/*****************获取随机坐标的函数*****************/this.getPos = function () //{
//横坐标200--1200this.x = Math.random() * window.innerWidth; //窗口高度
//纵坐标小于600this.y = Math.random() * window.innerHeight; //窗口宽度}/****绘制流星***************************/this.draw = function () //绘制一个流星的函数{context.save();context.beginPath();context.lineWidth = 1; //宽度context.globalAlpha = this.alpha; //设置透明度
//创建横向渐变颜色,起点坐标至终点坐标var line = context.createLinearGradient(this.x, this.y,this.x + this.width,this.y - this.height);
//分段设置颜色line.addColorStop(0, "white");line.addColorStop(0.3, this.color1);line.addColorStop(0.6, this.color2);context.strokeStyle = line;
//起点context.moveTo(this.x, this.y);
//终点context.lineTo(this.x + this.width, this.y - this.height);context.closePath();context.stroke();context.restore();}this.move = function () {
//清空流星像素var x = this.x + this.width - this.offset_x;var y = this.y - this.height;context.clearRect(x - 3, y - 3, this.offset_x + 5, this.offset_y + 5);
// context.strokeStyle="red";
// context.strokeRect(x,y-1,this.offset_x+1,this.offset_y+1);
//重新计算位置,往左下移动this.countPos();
//透明度增加this.alpha -= 0.002;
//重绘this.draw();}}//绘制流星function playRains() {for (var n = 0; n < rainCount; n++) {var rain = rains[n];rain.move();//移动if (rain.y > window.innerHeight) {//超出界限后重来context.clearRect(rain.x, rain.y - rain.height, rain.width, rain.height);rains[n] = new MeteorRain();rains[n].init();}}setTimeout("playRains()", 2);}/*流星雨结束*/</script><style type="text/css">body {background-color: black;}body, html {width: 100%;height: 100%;overflow: hidden;}</style>
</head>
<body>
<canvas id="stars"></canvas>
</body>
</html>

可在web页面内自定义展示文字,默认为“我爱你”

<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>烟花特效</title><script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script><style>html{width: 100%;overflow: hidden;}body {margin: 0;padding: 0;display: flex;justify-content: center;align-items: center;height: 100vh;background-color: black;}#fireworkInput {position: absolute;top: 20px;left: 50%;transform: translateX(-50%);}</style>
</head>
<body><input type="text" id="fireworkInput" placeholder="请输入文字" oninput="updateFireworkText(this.value)"><script>
let fireworks = [];
let gravity;
let fireworkText = "我爱你"; // 默认文字function setup() {createCanvas(windowWidth, windowHeight);colorMode(RGB);gravity = createVector(0, 0.2);stroke(255);strokeWeight(4);textSize(18);textAlign(CENTER, CENTER);
}function draw() {background(0, 25); // 淡化背景以创建拖尾效果if (random(1) < 0.05) {fireworks.push(new Firework(fireworkText));}for (let firework of fireworks) {firework.update();firework.show();}fireworks = fireworks.filter(firework => !firework.done());
}function updateFireworkText(value) {fireworkText = value || "我爱你"; // 如果输入为空,则默认显示“我爱你”
}class Firework {constructor(text) {this.hu = random(255);this.firework = new Particle(random(width), height, this.hu, true, text, true);this.exploded = false;this.particles = [];}update() {if (!this.exploded) {this.firework.applyForce(gravity);this.firework.update();if (this.firework.vel.y >= 0) {this.exploded = true;this.explode();}}for (let particle of this.particles) {particle.applyForce(gravity);particle.update();}this.particles = this.particles.filter(particle => !particle.done());}explode() {// 创建一个大的带文本的粒子let mainParticle = new Particle(this.firework.pos.x, this.firework.pos.y, this.hu, false, this.firework.text, true);this.particles.push(mainParticle);// 创建一系列小的彩色粒子let explosionAmount = random(100, 150);for (let i = 0; i < explosionAmount; i++) {let p = new Particle(this.firework.pos.x, this.firework.pos.y, random(255), false, '', false);this.particles.push(p);}}show() {if (!this.exploded) {this.firework.show();}for (let particle of this.particles) {particle.show();}}done() {return this.exploded && this.particles.length === 0;}
}class Particle {constructor(x, y, hu, firework, text, isMain) {this.pos = createVector(x, y);this.firework = firework;this.lifespan = 255;this.hu = hu;this.text = text;this.isMain = isMain; // 新增标志,表示是否为烟花主体if (this.firework) {this.vel = createVector(0, random(-18, -12));} else {this.vel = p5.Vector.random2D();this.vel.mult(random(4, 20));}this.acc = createVector(0, 0);}applyForce(force) {this.acc.add(force);}update() {if (!this.firework) {this.vel.mult(0.95);this.lifespan -= 4;}this.vel.add(this.acc);this.pos.add(this.vel);this.acc.mult(0);}done() {return this.lifespan < 0;}show() {colorMode(HSB);if (!this.firework) {strokeWeight(4);stroke(this.hu, 255, 255, this.lifespan);} else {strokeWeight(4);stroke(this.hu, 255, 255);}point(this.pos.x, this.pos.y);if (this.isMain) {fill(this.hu, 255, 255, this.lifespan);textSize(32); // 增加文本大小text(this.text, this.pos.x, this.pos.y);}}
}</script>
</body>
</html>

缤纷光点

<!DOCTYPE html>
<html>
<head><style>body {background-color: black;}canvas {display: block;position: absolute;top: 0;left: 0;}</style>
</head>
<body><canvas id="canvas"></canvas><script>const canvas = document.getElementById("canvas");const ctx = canvas.getContext("2d");let w, h, particles;class Particle {constructor() {this.x = Math.random() * w;this.y = Math.random() * h;this.vx = Math.random() * 10 - 5;this.vy = Math.random() * 10 - 5;this.radius = Math.random() * 3 + 1;this.color = `rgb(${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)}, ${Math.floor(Math.random() * 255)})`;}draw() {ctx.beginPath();ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);ctx.fillStyle = this.color;ctx.fill();}update() {this.x += this.vx;this.y += this.vy;if (this.x < 0 || this.x > w) this.vx = -this.vx;if (this.y < 0 || this.y > h) this.vy = -this.vy;}}function init() {w = canvas.width = window.innerWidth;h = canvas.height = window.innerHeight;particles = [];for (let i = 0; i < 100; i++) particles.push(new Particle());}function draw() {ctx.clearRect(0, 0, w, h);particles.forEach(p => p.draw());particles.forEach(p => p.update());requestAnimationFrame(draw);}init();draw();</script>
</body>
</html>

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com