下载使用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>
相关推荐
迷茫运维路13 分钟前
Jenkins声明式Pipeline流水线语法示例
运维·pipeline·jenkins·声明式
Zfox_30 分钟前
【Linux】进程信号全攻略(二)
linux·运维·c语言·c++
安於宿命35 分钟前
【Linux】简易版shell
linux·运维·服务器
追梦不止~42 分钟前
Docker常用命令+详解
运维·docker·容器
黑龙江亿林等保1 小时前
深入探索哈尔滨二级等保下的负载均衡SLB及其核心算法
运维·算法·负载均衡
黄小耶@1 小时前
linux常见命令
linux·运维·服务器
叫我龙翔1 小时前
【计网】实现reactor反应堆模型 --- 框架搭建
linux·运维·网络
古驿幽情1 小时前
CentOS AppStream 8 手动更新 yum源
linux·运维·centos·yum
BillKu1 小时前
Linux(CentOS)安装 Nginx
linux·运维·nginx·centos
BillKu1 小时前
Linux(CentOS)yum update -y 事故
linux·运维·centos