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

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

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

bash 复制代码
vim /etc/yum.repos.d/nginx.repo
bash 复制代码
[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

bash 复制代码
yum -y install nginx

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

bash 复制代码
cd /usr/share/nginx/html
vim index.html

鼠标拖拽,装酷耍帅

bash 复制代码
<!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>

陪你去看流星雨

bash 复制代码
<!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>

逼真烟花

bash 复制代码
<!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>

灿烂烟花

bash 复制代码
<!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);
      });
 
      // init
      ctx.fillStyle = '#000';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      // objects
      var 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 firework
            var 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);
            }
            // reset
            fire.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() {
        // clear
        ctx.globalCompositeOperation = 'source-over';
        ctx.globalAlpha = 0.18;
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
 
        // re-draw
        ctx.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>

炫酷漩涡

bash 复制代码
<!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立体烟花特效

bash 复制代码
<!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>

浪漫爱心

bash 复制代码
<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 pool
                particles = 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 queue
                firstFree++;
                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 particles
                if (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 particles
                while (particles[firstActive].age >= duration && firstActive != firstFree) {
                    firstActive++;
                    if (firstActive == particles.length) firstActive = 0;
                }
            };
            ParticlePool.prototype.draw = function (context, image) {
                // draw active particles
                if (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/sec
                time;
            // get point on heart with -PI <= t <= PI
            function 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 canvas
            var 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 path
                function 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 path
                context.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 fill
                context.fillStyle = '#ea80b0';
                context.fill();
                // create the image
                var image = new Image();
                image.src = canvas.toDataURL();
                return image;
            })();
            // render that thing!
            function render() {
                // next animation frame
                requestAnimationFrame(render);
                // update time
                var newTime = new Date().getTime() / 1000,
                    deltaTime = newTime - (time || newTime);
                time = newTime;
                // clear canvas
                context.clearRect(0, 0, canvas.width, canvas.height);
                // create new particles
                var 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 particles
                particles.update(deltaTime);
                particles.draw(context, image);
            }
            // handle (re-)sizing of the canvas
            function onResize() {
                canvas.width = canvas.clientWidth;
                canvas.height = canvas.clientHeight;
            }
            window.onresize = onResize;
            // delay rendering bootstrap
            setTimeout(function () {
                onResize();
                render();
            }, 10);
        })(document.getElementById('pinkboard'));
        </script>
</body>

</html>

流星雨

bash 复制代码
<!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--1200
                this.x = Math.random() * window.innerWidth; //窗口高度
//纵坐标小于600
                this.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页面内自定义展示文字,默认为"我爱你"

bash 复制代码
<!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>

缤纷光点

bash 复制代码
<!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>
相关推荐
wewowilling16 小时前
认识微服务
java·运维·微服务·springcloud
玖石书16 小时前
Ubuntu 24.04 UFW 全命令攻略:从入门到端口开放实战
linux·运维·ubuntu
weifont18 小时前
自动化抓取和上架商品:能不动手,就少动手
运维·前端·自动化
拂拉氏19 小时前
【知识讲解】 Linux之动静态库的了解
linux·运维·服务器·动静态库
Mikko719 小时前
Linux 重启后日志没了?进程被 OOM 杀了去哪看?logrotate 之后日志还往旧文件写?三台真机实测日志与性能排查
linux·运维·centos
光依旧20 小时前
发版就掉请求?Spring Boot优雅停机的四步
运维·spring boot·nginx·高可用·优雅停机·mcp server
yichengerp21 小时前
电子行业常用erp系统有哪些?推荐适合中小电子厂的易呈erp软件,操作简单,易落地
大数据·运维·人工智能·云计算·制造
天国梦21 小时前
国密门禁品牌怎么选?2026年最新3个避坑要点
运维·服务器·数据库·python
信可维1 天前
自动化操作多平台账号:登录态、双会话与 User Data 的 3 个坑
运维·自动化
龙仔7251 天前
RustDesk 自建服务器 Key 强校验配置笔记
运维·服务器·笔记