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

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

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

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

保存退出后下载nginx

yum -y install nginx

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

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

鼠标拖拽,装酷耍帅

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


</script>
</head>

<body style="margin:0px;padding:0px;width:100%;height:100%;overflow:hidden;">
<canvas id="c"></canvas>
</body>
</html>

陪你去看流星雨

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <title>Title</title>

    <style>

        *{

            margin: 0;

            padding: 0;

        }

        body{

            background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);

            height: 100vh;        /* == height: 100%  */

            overflow: hidden;

            font-family: 'Times New Roman', Times, serif;

            justify-content: center;

            align-items: center;

        }



        .container{

            position: relative;

            margin:0px;

            width: 100%;

            height: 100%;

            -webkit-transform: rotate(45deg);

            transform: rotateZ(45deg);

            -webkit-animation: sky 200000ms linear infinite;

            animation: sky 200000ms linear infinite;

        }



        .meteor{

            position: absolute;

            center:50%;

            height: 2px;

            background: linear-gradient(-45deg, #5f91ff, rgba(0, 0, 255, 0));

            border-radius: 999px;

            -webkit-filter: drop-shadow(0 0 6px #699bff);

            filter: drop-shadow(0 0 6px #699bff);

            -webkit-animation: tail 3000ms ease-in-out infinite, shooting 3000ms ease-in-out infinite;

            animation: tail 3000ms ease-in-out infinite, shooting 3000ms ease-in-out infinite;

        }



        .meteor::before, .meteor::after{

            content: '';

            position: absolute;

            top: calc(50% - 1px);

            right: 0;

            height: 2px;

            background: linear-gradient(-45deg, rgba(0, 0, 255, 0), #5f91ff, rgba(0, 0, 255, 0) );

            -webkit-transform: translateX(50%) rotateZ(45deg);

            transform: translateX(50%) rotateZ(45deg);

            border-radius: 100%;

            -webkit-animation: shining 3000ms ease-in-out infinite;

            animation: shining 3000ms ease-in-out infinite;

        }



        .meteor::after{

            -webkit-transform: translateX(50%) rotateZ(-45deg);

            transform: translateX(50%) rotateZ(-45deg);

        }



        /* 1 */

        .meteor:nth-child(1){

            top: calc(50% - 185px);

            left: calc(50% - 150px);

            -webkit-animation-delay: 8888ms;

            animation-delay: 8888ms;

        }

        .meteor:nth-child(1)::before, .meteor:nth-child(1)::after, .meteor:nth-child(1)::after{

            -webkit-animation-delay: 8888ms;

            animation-delay: 8888ms;

        }

        /* 2 */

        .meteor:nth-child(2){

            top: calc(50% - 50px);

            left: calc(50% - 180px);

            -webkit-animation-delay: 9288ms;

            animation-delay: 9288ms;

        }

        .meteor:nth-child(2)::before, .meteor:nth-child(2)::after, .meteor:nth-child(2)::after{

            -webkit-animation-delay: 9288ms;

            animation-delay: 9288ms;

        }



        .meteor:nth-child(3){

            top: calc(50% - 145px);

            left: calc(50% - 135px);

            -webkit-animation-delay: 8600ms;

            animation-delay: 8600ms;

        }

        .meteor:nth-child(3)::before, .meteor:nth-child(3)::after, .meteor:nth-child(3)::after{

            -webkit-animation-delay: 8600ms;

            animation-delay: 8600ms;

        }



        .meteor:nth-child(4){

            top: calc(50% - 78px);

            left: calc(50% - 155px);

            -webkit-animation-delay: 3288ms;

            animation-delay: 3288ms;

        }

        .meteor:nth-child(4)::before, .meteor:nth-child(4)::after, .meteor:nth-child(4)::after{

            -webkit-animation-delay: 3288ms;

            animation-delay: 3288ms;

        }



        .meteor:nth-child(5){

            top: calc(50% - 183px);

            left: calc(50% - 8px);

            -webkit-animation-delay: 5588ms;

            animation-delay: 5588ms;

        }

        .meteor:nth-child(5)::before, .meteor:nth-child(5)::after, .meteor:nth-child(5)::after{

            -webkit-animation-delay: 5588ms;

            animation-delay: 5588ms;

        }



        .meteor:nth-child(6){

            top: calc(50% - 30px);

            left: calc(50% - 195px);

            -webkit-animation-delay: 9388ms;

            animation-delay: 9388ms;

        }

        .meteor:nth-child(6)::before, .meteor:nth-child(6)::after, .meteor:nth-child(6)::after{

            -webkit-animation-delay: 9388ms;

            animation-delay: 9388ms;

        }



        .meteor:nth-child(7){

            top: calc(50% - 95px);

            left: calc(50% - 70px);

            -webkit-animation-delay: 2588ms;

            animation-delay: 2588ms;

        }

        .meteor:nth-child(7)::before, .meteor:nth-child(7)::after, .meteor:nth-child(7)::after{

            -webkit-animation-delay: 2588ms;

            animation-delay: 2588ms;

        }



        .meteor:nth-child(8){

            top: calc(50% - 60px);

            left: calc(50% - 70px);

            -webkit-animation-delay: 5288ms;

            animation-delay: 5288ms;

        }

        .meteor:nth-child(8)::before, .meteor:nth-child(8)::after, .meteor:nth-child(8)::after{

            -webkit-animation-delay: 5288ms;

            animation-delay: 5288ms;

        }



        .meteor:nth-child(9){

            top: calc(50% - 75px);

            left: calc(50% - 250px);

            -webkit-animation-delay: 888ms;

            animation-delay: 888ms;

        }

        .meteor:nth-child(9)::before, .meteor:nth-child(9)::after, .meteor:nth-child(9)::after{

            -webkit-animation-delay: 888ms;

            animation-delay: 888ms;

        }



        .meteor:nth-child(9){

            top: calc(50% - 76px);

            left: calc(50% - 240px);

            -webkit-animation-delay: 2388ms;

            animation-delay: 2388ms;

        }

        .meteor:nth-child(9)::before, .meteor:nth-child(9)::after, .meteor:nth-child(9)::after{

            -webkit-animation-delay: 2388ms;

            animation-delay: 2388ms;

        }



        .meteor:nth-child(10){

            top: calc(50% - 85px);

            left: calc(50% - 6px);

            -webkit-animation-delay: 3588ms;

            animation-delay: 3588ms;

        }

        .meteor:nth-child(10)::before, .meteor:nth-child(10)::after, .meteor:nth-child(10)::after{

            -webkit-animation-delay: 3588ms;

            animation-delay: 3588ms;

        }



        .meteor:nth-child(11){

            top: calc(50% - 135px);

            left: calc(50% - 260px);

            -webkit-animation-delay: 2888ms;

            animation-delay: 2888ms;

        }

        .meteor:nth-child(11)::before, .meteor:nth-child(11)::after, .meteor:nth-child(11)::after{

            -webkit-animation-delay: 2888ms;

            animation-delay: 2888ms;

        }



        .meteor:nth-child(12){

            top: calc(50% - 15px);

            left: calc(50% - 8px);

            -webkit-animation-delay: 388ms;

            animation-delay: 388ms;

        }

        .meteor:nth-child(12)::before, .meteor:nth-child(12)::after, .meteor:nth-child(12)::after{

            -webkit-animation-delay: 388ms;

            animation-delay: 388ms;

        }



        .meteor:nth-child(13){

            top: calc(50% - 155px);

            left: calc(50% - 50px);

            -webkit-animation-delay: 7288ms;

            animation-delay: 7288ms;

        }

        .meteor:nth-child(13)::before, .meteor:nth-child(13)::after, .meteor:nth-child(13)::after{

            -webkit-animation-delay: 7288ms;

            animation-delay: 7288ms;

        }



        .meteor:nth-child(14){

            top: calc(50% - 28px);

            left: calc(50% - 80px);

            -webkit-animation-delay: 8888ms;

            animation-delay: 8888ms;

        }

        .meteor:nth-child(14)::before, .meteor:nth-child(14)::after, .meteor:nth-child(14)::after{

            -webkit-animation-delay: 8888ms;

            animation-delay: 8888ms;

        }



        .meteor:nth-child(15){

            top: calc(50% - 35px);

            left: calc(50% - 200px);

            -webkit-animation-delay: 7588ms;

            animation-delay: 7588ms;

        }

        .meteor:nth-child(15)::before, .meteor:nth-child(15)::after, .meteor:nth-child(15)::after{

            -webkit-animation-delay: 7588ms;

            animation-delay: 7588ms;

        }



        .meteor:nth-child(16){

            top: calc(50% - 40px);

            left: calc(50% - 250px);

            -webkit-animation-delay: 1888ms;

            animation-delay: 1888ms;

        }

        .meteor:nth-child(16)::before, .meteor:nth-child(16)::after, .meteor:nth-child(16)::after{

            -webkit-animation-delay: 1888ms;

            animation-delay: 1888ms;

        }





        @-webkit-keyframes tail{

            0%{

                width: 0;

            }

            30%{

                width: 100px;

            }

            100%{

                width: 0;

            }

        }

        @keyframes tail{

            0%{

                width: 0;

            }

            30%{

                width: 100px;

            }

            100%{

                width: 0;

            }

        }



        @-webkit-keyframes shining{

            0%{

                width: 0;

            }

            50%{

                width: 30px;

            }

            1000%{

                width: 0;

            }

        }

        @keyframes shining{

            0%{

                width: 0;

            }

            50%{

                width: 30px;

            }

            1000%{

                width: 0;

            }

        }



        @-webkit-keyframes shooting{

            0%{

                -webkit-transform: translateX(0);

                transform: translateX(0);

            }

            100%{

                -webkit-transform: translateX(300px);

                transform: translateX(300px);

            }

        }

        @keyframes shooting{

            0%{

                -webkit-transform: translateX(0);

                transform: translateX(0);

            }

            100%{

                -webkit-transform: translateX(300px);

                transform: translateX(300px);

            }

        }



        @-webkit-keyframes sky{

            0%{

                -webkit-transform: rotate(45deg);

                transform: rotate(45deg);

            }

            100%{

                -webkit-transform: rotate(405deg);

                transform: rotate(405deg);

            }

        }

        @keyframes sky{

            0%{

                -webkit-transform: rotate(45deg);

                transform: rotate(45deg);

            }

            100%{

                -webkit-transform: rotate(405deg);

                transform: rotate(405deg);

            }

        }

    </style>

</head>

<body>

<div class="container">

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>

    <div class="meteor"></div>



</div>

</body>

</html>

逼真烟花

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

灿烂烟花

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
 
<head>
  <title> 真实烟花</title>
  
  <style>
    body {
      padding: 0;
    }
 
    canvas {
      display: block;
    }
  </style>
</head>
 
<body>
  <canvas id="canvas"></canvas>
  <script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
  <script>
    $(function () {
      var canvas = $('#canvas')[0];
      canvas.width = $(window).width();
      canvas.height = $(window).height();
      var ctx = canvas.getContext('2d');
 
      // resize
      $(window).on('resize', function () {
        canvas.width = $(window).width();
        canvas.height = $(window).height();
        ctx.fillStyle = '#000';
        ctx.fillRect(0, 0, canvas.width, canvas.height);
      });
 
      // 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>

 炫酷漩涡

<!doctype html>

<html>

<head>

    <meta charset="utf-8">

    <title>H5,200行代码实现粒子漩涡特效</title>

    <style>

        html,body{

            margin:0px;

            width:100%;

            height:100%;

            overflow:hidden;

            background: #000000;

        }

        #canvas{

            position:absolute;

            width:100%;

            height:100%;

        }

    </style>

</head>

<body>

<canvas id="canvas"></canvas>

<script>

    function project3D(x,y,z,vars){

        var p,d;

        x-=vars.camX;

        y-=vars.camY-8;

        z-=vars.camZ;

        p=Math.atan2(x,z);

        d=Math.sqrt(x*x+z*z);

        x=Math.sin(p-vars.yaw)*d;

        z=Math.cos(p-vars.yaw)*d;

        p=Math.atan2(y,z);

        d=Math.sqrt(y*y+z*z);

        y=Math.sin(p-vars.pitch)*d;

        z=Math.cos(p-vars.pitch)*d;

        var rx1=-1000;

        var ry1=1;

        var rx2=1000;

        var ry2=1;

        var rx3=0;

        var ry3=0;

        var rx4=x;

        var ry4=z;

        var uc=(ry4-ry3)*(rx2-rx1)-(rx4-rx3)*(ry2-ry1);

        var ua=((rx4-rx3)*(ry1-ry3)-(ry4-ry3)*(rx1-rx3))/uc;

        var ub=((rx2-rx1)*(ry1-ry3)-(ry2-ry1)*(rx1-rx3))/uc;

        if(!z)z=0.000000001;

        if(ua>0&&ua<1&&ub>0&&ub<1){

            return {

                x:vars.cx+(rx1+ua*(rx2-rx1))*vars.scale,

                y:vars.cy+y/z*vars.scale,

                d:(x*x+y*y+z*z)

            };

        }else{

            return { d:-1 };

        }

    }

    function elevation(x,y,z){

        var dist = Math.sqrt(x*x+y*y+z*z);

        if(dist && z/dist>=-1 && z/dist <=1) return Math.acos(z / dist);

        return 0.00000001;

    }

    function rgb(col){

        col += 0.000001;

        var r = parseInt((0.5+Math.sin(col)*0.5)*16);

        var g = parseInt((0.5+Math.cos(col)*0.5)*16);

        var b = parseInt((0.5-Math.sin(col)*0.5)*16);

        return "#"+r.toString(16)+g.toString(16)+b.toString(16);

    }

    function interpolateColors(RGB1,RGB2,degree){

        var w2=degree;

        var w1=1-w2;

        return [w1*RGB1[0]+w2*RGB2[0],w1*RGB1[1]+w2*RGB2[1],w1*RGB1[2]+w2*RGB2[2]];

    }

    function rgbArray(col){

        col += 0.000001;

        var r = parseInt((0.5+Math.sin(col)*0.5)*256);

        var g = parseInt((0.5+Math.cos(col)*0.5)*256);

        var b = parseInt((0.5-Math.sin(col)*0.5)*256);

        return [r, g, b];

    }

    function colorString(arr){

        var r = parseInt(arr[0]);

        var g = parseInt(arr[1]);

        var b = parseInt(arr[2]);

        return "#"+("0" + r.toString(16) ).slice (-2)+("0" + g.toString(16) ).slice (-2)+("0" + b.toString(16) ).slice (-2);

    }

    function process(vars){

        if(vars.points.length<vars.initParticles) for(var i=0;i<5;++i) spawnParticle(vars);

        var p,d,t;

        p = Math.atan2(vars.camX, vars.camZ);

        d = Math.sqrt(vars.camX * vars.camX + vars.camZ * vars.camZ);

        d -= Math.sin(vars.frameNo / 80) / 25;

        t = Math.cos(vars.frameNo / 300) / 165;

        vars.camX = Math.sin(p + t) * d;

        vars.camZ = Math.cos(p + t) * d;

        vars.camY = -Math.sin(vars.frameNo / 220) * 15;

        vars.yaw = Math.PI + p + t;

        vars.pitch = elevation(vars.camX, vars.camZ, vars.camY) - Math.PI / 2;

        var t;

        for(var i=0;i<vars.points.length;++i){

            x=vars.points[i].x;

            y=vars.points[i].y;

            z=vars.points[i].z;

            d=Math.sqrt(x*x+z*z)/1.0075;

            t=.1/(1+d*d/5);

            p=Math.atan2(x,z)+t;

            vars.points[i].x=Math.sin(p)*d;

            vars.points[i].z=Math.cos(p)*d;

            vars.points[i].y+=vars.points[i].vy*t*((Math.sqrt(vars.distributionRadius)-d)*2);

            if(vars.points[i].y>vars.vortexHeight/2 || d<.25){

                vars.points.splice(i,1);

                spawnParticle(vars);

            }

        }

    }

    function drawFloor(vars){

        var x,y,z,d,point,a;

        for (var i = -25; i <= 25; i += 1) {

            for (var j = -25; j <= 25; j += 1) {

                x = i*2;

                z = j*2;

                y = vars.floor;

                d = Math.sqrt(x * x + z * z);

                point = project3D(x, y-d*d/85, z, vars);

                if (point.d != -1) {

                    size = 1 + 15000 / (1 + point.d);

                    a = 0.15 - Math.pow(d / 50, 4) * 0.15;

                    if (a > 0) {

                        vars.ctx.fillStyle = colorString(interpolateColors(rgbArray(d/26-vars.frameNo/40),[0,128,32],.5+Math.sin(d/6-vars.frameNo/8)/2));

                        vars.ctx.globalAlpha = a;

                        vars.ctx.fillRect(point.x-size/2,point.y-size/2,size,size);

                    }

                }

            }

        }

        vars.ctx.fillStyle = "#82f";

        for (var i = -25; i <= 25; i += 1) {

            for (var j = -25; j <= 25; j += 1) {

                x = i*2;

                z = j*2;

                y = -vars.floor;

                d = Math.sqrt(x * x + z * z);

                point = project3D(x, y+d*d/85, z, vars);

                if (point.d != -1) {

                    size = 1 + 15000 / (1 + point.d);

                    a = 0.15 - Math.pow(d / 50, 4) * 0.15;

                    if (a > 0) {

                        vars.ctx.fillStyle = colorString(interpolateColors(rgbArray(-d/26-vars.frameNo/40),[32,0,128],.5+Math.sin(-d/6-vars.frameNo/8)/2));

                        vars.ctx.globalAlpha = a;

                        vars.ctx.fillRect(point.x-size/2,point.y-size/2,size,size);

                    }

                }

            }

        }

    }

    function sortFunction(a,b){

        return b.dist-a.dist;

    }

    function draw(vars){

        vars.ctx.globalAlpha=.15;

        vars.ctx.fillStyle="#000";

        vars.ctx.fillRect(0, 0, canvas.width, canvas.height);

        drawFloor(vars);

        var point,x,y,z,a;

        for(var i=0;i<vars.points.length;++i){

            x=vars.points[i].x;

            y=vars.points[i].y;

            z=vars.points[i].z;

            point=project3D(x,y,z,vars);

            if(point.d != -1){

                vars.points[i].dist=point.d;

                size=1+vars.points[i].radius/(1+point.d);

                d=Math.abs(vars.points[i].y);

                a = .8 - Math.pow(d / (vars.vortexHeight/2), 1000) * .8;

                vars.ctx.globalAlpha=a>=0&&a<=1?a:0;

                vars.ctx.fillStyle=rgb(vars.points[i].color);

                if(point.x>-1&&point.x<vars.canvas.width&&point.y>-1&&point.y<vars.canvas.height)vars.ctx.fillRect(point.x-size/2,point.y-size/2,size,size);

            }

        }

        vars.points.sort(sortFunction);

    }

    function spawnParticle(vars){



        var p,ls;

        pt={};

        p=Math.PI*2*Math.random();

        ls=Math.sqrt(Math.random()*vars.distributionRadius);

        pt.x=Math.sin(p)*ls;

        pt.y=-vars.vortexHeight/2;

        pt.vy=vars.initV/20+Math.random()*vars.initV;

        pt.z=Math.cos(p)*ls;

        pt.radius=200+800*Math.random();

        pt.color=pt.radius/1000+vars.frameNo/250;

        vars.points.push(pt);

    }

    function frame(vars) {

        if(vars === undefined){

            var vars={};

            vars.canvas = document.querySelector("canvas");

            vars.ctx = vars.canvas.getContext("2d");

            vars.canvas.width = document.body.clientWidth;

            vars.canvas.height = document.body.clientHeight;

            window.addEventListener("resize", function(){

                vars.canvas.width = document.body.clientWidth;

                vars.canvas.height = document.body.clientHeight;

                vars.cx=vars.canvas.width/2;

                vars.cy=vars.canvas.height/2;

            }, true);

            vars.frameNo=0;



            vars.camX = 0;

            vars.camY = 0;

            vars.camZ = -14;

            vars.pitch = elevation(vars.camX, vars.camZ, vars.camY) - Math.PI / 2;

            vars.yaw = 0;

            vars.cx=vars.canvas.width/2;

            vars.cy=vars.canvas.height/2;

            vars.bounding=10;

            vars.scale=500;

            vars.floor=26.5;



            vars.points=[];

            vars.initParticles=700;

            vars.initV=.01;

            vars.distributionRadius=800;

            vars.vortexHeight=25;

        }

        vars.frameNo++;

        requestAnimationFrame(function() {

            frame(vars);

        });

        process(vars);

        draw(vars);

    }

    frame();

</script>

</body>

</html>

3D立体烟花特效

<!doctype html>

<html>

<head>

<meta charset="utf-8">

<title>烟花特效</title>

<style>

html,body{

   margin:0px;

   width:100%;

   height:100%;

   overflow:hidden;

   background:#000;

}

#canvas{

   width:100%;

   height:100%;

}

</style>

</head>

<body>

<canvas id="canvas"></canvas><script>

function initVars(){

   pi=Math.PI;

   ctx=canvas.getContext("2d");

   canvas.width=canvas.clientWidth;

   canvas.height=canvas.clientHeight;

   cx=canvas.width/2;

   cy=canvas.height/2;

   playerZ=-25;

   playerX=playerY=playerVX=playerVY=playerVZ=pitch=yaw=pitchV=yawV=0;

   scale=600;

   seedTimer=0;seedInterval=5,seedLife=100;gravity=.02;

   seeds=new Array();

   sparkPics=new Array();

   s="https://cantelope.org/NYE/";

   for(i=1;i<=10;++i){

      sparkPic=new Image();

      sparkPic.src=s+"spark"+i+".png";

      sparkPics.push(sparkPic);

   }

   sparks=new Array();

   pow1=new Audio(s+"pow1.ogg");

   pow2=new Audio(s+"pow2.ogg");

   pow3=new Audio(s+"pow3.ogg");

   pow4=new Audio(s+"pow4.ogg");

   frames = 0;

}

function rasterizePoint(x,y,z){

   var p,d;

   x-=playerX;

   y-=playerY;

   z-=playerZ;

   p=Math.atan2(x,z);

   d=Math.sqrt(x*x+z*z);

   x=Math.sin(p-yaw)*d;

   z=Math.cos(p-yaw)*d;

   p=Math.atan2(y,z);

   d=Math.sqrt(y*y+z*z);

   y=Math.sin(p-pitch)*d;

   z=Math.cos(p-pitch)*d;

   var rx1=-1000,ry1=1,rx2=1000,ry2=1,rx3=0,ry3=0,rx4=x,ry4=z,uc=(ry4-ry3)*(rx2-rx1)-(rx4-rx3)*(ry2-ry1);

   if(!uc) return {x:0,y:0,d:-1};

   var ua=((rx4-rx3)*(ry1-ry3)-(ry4-ry3)*(rx1-rx3))/uc;

   var ub=((rx2-rx1)*(ry1-ry3)-(ry2-ry1)*(rx1-rx3))/uc;

   if(!z)z=.000000001;

   if(ua>0&&ua<1&&ub>0&&ub<1){

      return {

         x:cx+(rx1+ua*(rx2-rx1))*scale,

         y:cy+y/z*scale,

         d:Math.sqrt(x*x+y*y+z*z)

      };

   }else{

      return {

         x:cx+(rx1+ua*(rx2-rx1))*scale,

         y:cy+y/z*scale,

         d:-1

      };

   }

}

function spawnSeed(){

   seed=new Object();

   seed.x=-50+Math.random()*100;

   seed.y=25;

   seed.z=-50+Math.random()*100;

   seed.vx=.1-Math.random()*.2;

   seed.vy=-1.5;//*(1+Math.random()/2);

   seed.vz=.1-Math.random()*.2;

   seed.born=frames;

   seeds.push(seed);

}

function splode(x,y,z){

   t=5+parseInt(Math.random()*150);

   sparkV=1+Math.random()*2.5;

   type=parseInt(Math.random()*3);

   switch(type){

      case 0:

         pic1=parseInt(Math.random()*10);

         break;

      case 1:

         pic1=parseInt(Math.random()*10);

         do{ pic2=parseInt(Math.random()*10); }while(pic2==pic1);

         break;

      case 2:

         pic1=parseInt(Math.random()*10);

         do{ pic2=parseInt(Math.random()*10); }while(pic2==pic1);

         do{ pic3=parseInt(Math.random()*10); }while(pic3==pic1 || pic3==pic2);

         break;

   }

   for(m=1;m<t;++m){

      spark=new Object();

      spark.x=x; spark.y=y; spark.z=z;

      p1=pi*2*Math.random();

      p2=pi*Math.random();

      v=sparkV*(1+Math.random()/6)

      spark.vx=Math.sin(p1)*Math.sin(p2)*v;

      spark.vz=Math.cos(p1)*Math.sin(p2)*v;

      spark.vy=Math.cos(p2)*v;

      switch(type){

         case 0: spark.img=sparkPics[pic1]; break;

         case 1:

            spark.img=sparkPics[parseInt(Math.random()*2)?pic1:pic2];

            break;

         case 2:

            switch(parseInt(Math.random()*3)){

               case 0: spark.img=sparkPics[pic1]; break;

               case 1: spark.img=sparkPics[pic2]; break;

               case 2: spark.img=sparkPics[pic3]; break;

            }

            break;

      }

      spark.radius=25+Math.random()*50;

      spark.alpha=1;

      spark.trail=new Array();

      sparks.push(spark);

   }

   switch(parseInt(Math.random()*4)){

      case 0:    pow=new Audio(s+"pow1.ogg"); break;

      case 1:    pow=new Audio(s+"pow2.ogg"); break;

      case 2:    pow=new Audio(s+"pow3.ogg"); break;

      case 3:    pow=new Audio(s+"pow4.ogg"); break;

   }

   d=Math.sqrt((x-playerX)*(x-playerX)+(y-playerY)*(y-playerY)+(z-playerZ)*(z-playerZ));

   pow.volume=1.5/(1+d/10);



}

function doLogic(){

   if(seedTimer<frames){

      seedTimer=frames+seedInterval*Math.random()*10;

      spawnSeed();

   }

   for(i=0;i<seeds.length;++i){

      seeds[i].vy+=gravity;

      seeds[i].x+=seeds[i].vx;

      seeds[i].y+=seeds[i].vy;

      seeds[i].z+=seeds[i].vz;

      if(frames-seeds[i].born>seedLife){

         splode(seeds[i].x,seeds[i].y,seeds[i].z);

         seeds.splice(i,1);

      }

   }

   for(i=0;i<sparks.length;++i){

      if(sparks[i].alpha>0 && sparks[i].radius>5){

         sparks[i].alpha-=.01;

         sparks[i].radius/=1.02;

         sparks[i].vy+=gravity;

         point=new Object();

         point.x=sparks[i].x;

         point.y=sparks[i].y;

         point.z=sparks[i].z;

         if(sparks[i].trail.length){

            x=sparks[i].trail[sparks[i].trail.length-1].x;

            y=sparks[i].trail[sparks[i].trail.length-1].y;

            z=sparks[i].trail[sparks[i].trail.length-1].z;

            d=((point.x-x)*(point.x-x)+(point.y-y)*(point.y-y)+(point.z-z)*(point.z-z));

            if(d>9){

               sparks[i].trail.push(point);

            }

         }else{

            sparks[i].trail.push(point);

         }

         if(sparks[i].trail.length>5)sparks[i].trail.splice(0,1);

         sparks[i].x+=sparks[i].vx;

         sparks[i].y+=sparks[i].vy;

         sparks[i].z+=sparks[i].vz;

         sparks[i].vx/=1.075;

         sparks[i].vy/=1.075;

         sparks[i].vz/=1.075;

      }else{

         sparks.splice(i,1);

      }

   }

   p=Math.atan2(playerX,playerZ);

   d=Math.sqrt(playerX*playerX+playerZ*playerZ);

   d+=Math.sin(frames/80)/1.25;

   t=Math.sin(frames/200)/40;

   playerX=Math.sin(p+t)*d;

   playerZ=Math.cos(p+t)*d;

   yaw=pi+p+t;

}

function rgb(col){

   var r = parseInt((.5+Math.sin(col)*.5)*16);

   var g = parseInt((.5+Math.cos(col)*.5)*16);

   var b = parseInt((.5-Math.sin(col)*.5)*16);

   return "#"+r.toString(16)+g.toString(16)+b.toString(16);

}

function draw(){

   ctx.clearRect(0,0,cx*2,cy*2);

   ctx.fillStyle="#ff8";

   for(i=-100;i<100;i+=3){

      for(j=-100;j<100;j+=4){

         x=i;z=j;y=25;

         point=rasterizePoint(x,y,z);

         if(point.d!=-1){

            size=250/(1+point.d);

            d = Math.sqrt(x * x + z * z);

            a = 0.75 - Math.pow(d / 100, 6) * 0.75;

            if(a>0){

               ctx.globalAlpha = a;

               ctx.fillRect(point.x-size/2,point.y-size/2,size,size);

            }

         }

      }

   }

   ctx.globalAlpha=1;

   for(i=0;i<seeds.length;++i){

      point=rasterizePoint(seeds[i].x,seeds[i].y,seeds[i].z);

      if(point.d!=-1){

         size=200/(1+point.d);

         ctx.fillRect(point.x-size/2,point.y-size/2,size,size);

      }

   }

   point1=new Object();

   for(i=0;i<sparks.length;++i){

      point=rasterizePoint(sparks[i].x,sparks[i].y,sparks[i].z);

      if(point.d!=-1){

         size=sparks[i].radius*200/(1+point.d);

         if(sparks[i].alpha<0)sparks[i].alpha=0;

         if(sparks[i].trail.length){

            point1.x=point.x;

            point1.y=point.y;

            switch(sparks[i].img){

               case sparkPics[0]:ctx.strokeStyle="#f84";break;

               case sparkPics[1]:ctx.strokeStyle="#84f";break;

               case sparkPics[2]:ctx.strokeStyle="#8ff";break;

               case sparkPics[3]:ctx.strokeStyle="#fff";break;

               case sparkPics[4]:ctx.strokeStyle="#4f8";break;

               case sparkPics[5]:ctx.strokeStyle="#f44";break;

               case sparkPics[6]:ctx.strokeStyle="#f84";break;

               case sparkPics[7]:ctx.strokeStyle="#84f";break;

               case sparkPics[8]:ctx.strokeStyle="#fff";break;

               case sparkPics[9]:ctx.strokeStyle="#44f";break;

            }

            for(j=sparks[i].trail.length-1;j>=0;--j){

               point2=rasterizePoint(sparks[i].trail[j].x,sparks[i].trail[j].y,sparks[i].trail[j].z);

               if(point2.d!=-1){

                  ctx.globalAlpha=j/sparks[i].trail.length*sparks[i].alpha/2;

                  ctx.beginPath();

                  ctx.moveTo(point1.x,point1.y);

                  ctx.lineWidth=1+sparks[i].radius*10/(sparks[i].trail.length-j)/(1+point2.d);

                  ctx.lineTo(point2.x,point2.y);

                  ctx.stroke();

                  point1.x=point2.x;

                  point1.y=point2.y;

               }

            }

         }

         ctx.globalAlpha=sparks[i].alpha;



      }

   }

}

function frame(){

   if(frames>100000){

      seedTimer=0;

      frames=0;

   }

   frames++;

   draw();

   doLogic();

   requestAnimationFrame(frame);

}

window.addEventListener("resize",()=>{

   canvas.width=canvas.clientWidth;

   canvas.height=canvas.clientHeight;

   cx=canvas.width/2;

   cy=canvas.height/2;

});

initVars();

frame();

</script>

</body>

</html>

浪漫爱心

<html>
<head>
    <meta charset="utf-8">
    <title>loveHeart</title>
    <link rel="shortcut icon" href="http://zhouql.vip/images/心.png" type="image/x-icon">
    <style>
        html,
        body {
            height: 100%;
            padding: 0;
            margin: 0;
            background: #000;
        }
        canvas {
            position: absolute;
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <canvas id="pinkboard"></canvas>
    <script>
        var settings = {
            particles: {
                length: 500, 
                duration: 2, 
                velocity: 100, 
                effect: -0.75, 
                size: 32, 
            },
        };
        (function () { var b = 0; var c = ["ms", "moz", "webkit", "o"]; for (var a = 0; a < c.length && !window.requestAnimationFrame; ++a) { window.requestAnimationFrame = window[c[a] + "RequestAnimationFrame"]; window.cancelAnimationFrame = window[c[a] + "CancelAnimationFrame"] || window[c[a] + "CancelRequestAnimationFrame"] } if (!window.requestAnimationFrame) { window.requestAnimationFrame = function (h, e) { var d = new Date().getTime(); var f = Math.max(0, 16 - (d - b)); var g = window.setTimeout(function () { h(d + f) }, f); b = d + f; return g } } if (!window.cancelAnimationFrame) { window.cancelAnimationFrame = function (d) { clearTimeout(d) } } }());

        var Point = (function () {
            function Point(x, y) {
                this.x = (typeof x !== 'undefined') ? x : 0;
                this.y = (typeof y !== 'undefined') ? y : 0;
            }
            Point.prototype.clone = function () {
                return new Point(this.x, this.y);
            };
            Point.prototype.length = function (length) {
                if (typeof length == 'undefined')
                    return Math.sqrt(this.x * this.x + this.y * this.y);
                this.normalize();
                this.x *= length;
                this.y *= length;
                return this;
            };
            Point.prototype.normalize = function () {
                var length = this.length();
                this.x /= length;
                this.y /= length;
                return this;
            };
            return Point;
        })();
        var Particle = (function () {
            function Particle() {
                this.position = new Point();
                this.velocity = new Point();
                this.acceleration = new Point();
                this.age = 0;
            }
            Particle.prototype.initialize = function (x, y, dx, dy) {
                this.position.x = x;
                this.position.y = y;
                this.velocity.x = dx;
                this.velocity.y = dy;
                this.acceleration.x = dx * settings.particles.effect;
                this.acceleration.y = dy * settings.particles.effect;
                this.age = 0;
            };
            Particle.prototype.update = function (deltaTime) {
                this.position.x += this.velocity.x * deltaTime;
                this.position.y += this.velocity.y * deltaTime;
                this.velocity.x += this.acceleration.x * deltaTime;
                this.velocity.y += this.acceleration.y * deltaTime;
                this.age += deltaTime;
            };
            Particle.prototype.draw = function (context, image) {
                function ease(t) {
                    return (--t) * t * t + 1;
                }
                var size = image.width * ease(this.age / settings.particles.duration);
                context.globalAlpha = 1 - this.age / settings.particles.duration;
                context.drawImage(image, this.position.x - size / 2, this.position.y - size / 2, size, size);
            };
            return Particle;
        })();
        var ParticlePool = (function () {
            var particles,
                firstActive = 0,
                firstFree = 0,
                duration = settings.particles.duration;
            function ParticlePool(length) {
                // create and populate particle 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>

流星雨

<!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页面内自定义展示文字,默认为“我爱你”

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

缤纷光点

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/701057.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud-面试篇(二十五)

&#xff08;1&#xff09;Sentinel与Hystix的线程隔离有什么差别&#xff1f; &#xff08;2&#xff09;Sentinel的限流与Gateway限流有什么差别 固定窗口计数器算法&#xff0c;可能再其他的时间两个窗口的交界内超过了请求阈值 &#xff0c;所以就有了滑动窗口算法 滑动窗…

Docker引起的漏洞问题

前言 测试环境上的中间件和java应用都是由docker进行部署的,但是因为docker的镜像访问有时候需要外网,由此引发了问题,在docker文件中 /usr/lib/systemd/system/docker.service 原有的配置为,可以看到进行了加密 ExecStart/usr/bin/dockerd --tlsverify --tlscacert/etc/docker…

上海斯歌荣获“2023年度杰出数字化转型方案提供商”奖项

为表彰上海斯歌在各行业的数字化转型事业中所做出的突出贡献&#xff0c;经CIO时代、新基建创新研究院专家组评审认定&#xff0c;授予上海斯歌“2023年度杰出数字化转型方案提供商”奖项。荣获该殊荣&#xff0c;不仅是业界对上海斯歌解决方案专业能力及落地实施能力的又一次认…

Java课程设计:基于ssm的旅游管理系统系统(内附源码)

文章目录 一、项目介绍二、项目展示三、源码展示四、源码获取 一、项目介绍 2023年处于信息科技高速发展的大背景之下。在今天&#xff0c;缺少手机和电脑几乎已经成为不可能的事情&#xff0c;人们生活中已经难以离开手机和电脑。针对增加的成本管理和操作,各大旅行社非常必要…

java多线程相关概念

在Java多线程编程中&#xff0c;有几个关键的术语需要理解&#xff1a; 1.线程(Thread)&#xff1a;线程是操作系统能够进行运算调度的最小单位&#xff0c;它被包含在进程之中&#xff0c;是进程中的实际运作单位。 2.进程(Process)&#xff1a;进程是系统进行资源分配和调度…

Git使用-gitlab上面的项目如何整到本地的idea中

场景 一般我们在开发项目或者接手某个项目时&#xff0c;基本都要接触Git&#xff0c;比如上传项目代码&#xff0c;下载同事给你的交接代码等等。 这是一个基本功&#xff0c;小小整理一下日常操作中的使用。 第一步&#xff1a;在 GitLab 上找到你要克隆的项目&#xff0c;复…

PDF操作工具

PDF的转换、编辑、删除、文本识别、添加水印等等各种操作用的越来越多&#xff0c;相信很多朋友都有WPS等软件的会员、可是更多的朋友是没开通WPS等软件的会员的&#xff0c;那么怎么办呢&#xff0c;给你们推荐一款pdf操作的工具。 PDF24 Creator是一款免费且流行的 PDF 解决…

5 种技术,可用于系统中的大数据模型

文章目录 一、说明二、第一种&#xff1a;批量大小三、第二种&#xff1a;主动学习四、第三种&#xff1a;增加代币数量五、第四种&#xff1a; 稀疏激活六、第五种&#xff1a;过滤器和更简单的模型后记 一、说明 以下是本文重要观点的摘要。阅读它以获取更多详细信息/获取原…

在欧拉系统中搭建万里数据库MGR集群(图文详解)

在信创和国产化的大趋势下&#xff0c;将各个中间件进行国产化替换是当前非常重要的任务之一。下面将介绍如何在国产化欧拉系统中安装国产万里数据库。 0.MGR简介 MGR&#xff08;MySQL Group Replication&#xff09;&#xff1a;是MySQL官方提供的一种高可用性和容错性解决…

塔勒布作品集合风险共担来应对不确定性、风险、随机性的局限性

Nassim Nicholas Taleb 是一位著名的风险分析学者和作家&#xff0c;他的主要作品被合称为“Incerto”不确定性系列。这些书籍虽然可以独立阅读&#xff0c;但它们在主题和思想上紧密相连&#xff0c;共同探讨了不确定性、风险、随机性和人类在应对这些方面的局限性。 以下是 …

修改注册表默认端口号;telnet端口号失败、不通、没反应;访问另一机器端口不通

背景&#xff1a;在多集群项目中&#xff0c;发现访问其他机器不通。遂使用telnet命令试试&#xff0c;确实端口不通。也查看了防火墙策略等&#xff0c;最后尝试了修改注册表默认端口号。这样端口可通了。但并未实际解决问题&#xff0c;在实际项目中需要确认一下你实际项目中…

Keil MDK 下载安装相对应CPU的Software Packs

要下载MDK ARM的Software Packs&#xff0c;您可以按照以下步骤进行&#xff0c;这些步骤结合了参考文章中的信息并进行了适当的归纳和整理&#xff1a; 1. 访问Keil官网 打开浏览器&#xff0c;访问Keil的官方网站&#xff1a;www.keil.arm.com。 2. 进入Software Packs下载…

解析 Spring 框架中的三种 BeanName 生成策略

在 Spring 框架中&#xff0c;定义 Bean 时不一定需要指定名称&#xff0c;Spring 会智能生成默认名称。本文将介绍 Spring 的三种 BeanName 生成器&#xff0c;包括在 XML 配置、Java 注解和组件扫描中使用的情况&#xff0c;并解释它们如何自动创建和管理 Bean 名称。 1. Be…

STM32硬件接口I2C应用(基于MP6050)

目录 概述 1 STM32Cube控制配置I2C 1.1 I2C参数配置 1.2 使用STM32Cube产生工程 2 HAL库函数介绍 2.1 初始化函数 2.2 写数据函数 2.3 读数据函数 3 认识MP6050 3.1 MP6050功能介绍 3.2 加速计测量寄存器 ​编辑3.3 温度计量寄存器 3.4 陀螺仪测量寄存器 4 MP60…

WindTerm使用SSH密钥连接阿里云实例,服务器设置SSH密钥登录

安装Windterm 地址https://github.com/kingToolbox/WindTerm/releases 下载完放到文件夹就可以打开 阿里云开启密钥对 打开阿里云ecs控制台 https://ecs.console.aliyun.com/keyPair/region/cn-wulanchabu 网络与安全->密钥对&#xff0c;创建密钥对&#xff0c;创建成…

6.11 作业

以下是一个简单的比喻&#xff0c;将多态概念与生活中的实际情况相联系&#xff1a; 比喻&#xff1a;动物园的讲解员和动物表演 想象一下你去了一家动物园&#xff0c;看到了许多不同种类的动物&#xff0c;如狮子、大象、猴子等。现在&#xff0c;动物园里有一位讲解员&…

OA协同办公系统 iWebPDF插件安装

1、下载压缩文件 iweboffice&#xff0c;并进行解压 链接&#xff1a;https://pan.baidu.com/s/1GQd7000PTZ771ifL5KEflg 提取码&#xff1a;hb56 2、安装iWenpdf2018.exe 3、安装金格中间件外部应用 4、测试了谷歌、360安全&#xff0c;发现安装插件后&#xff0c;只有360极…

10秒变鬼短视频:四川鑫悦里文化传媒有限公司

10秒变鬼短视频&#xff1a;创意与惊悚的完美融合 在短视频的世界里&#xff0c;创新与独特性 节奏、巧妙的剪辑和惊悚的氛围&#xff0c;成为了许多观众喜爱的对象。四川鑫悦里文化传媒有限公司将探讨“10秒变鬼”短视频的创作技巧、受众心理以及其对短视频行业的启示。 一…

【ubuntu22.04~mysql-MHA-mycat】

ubuntu22.04~mysql-MHA-mycat 前言一、安装指定版本mysql-server(8.0.23)1、安装mysql2、启用修改mysql配置1、安装3、修改权限3.1、用户密码存放位置,3.2、创建用户root@%4、mysql配置文件my.cnf修改1、主节点my.cnf2、slave1~my.cnf修改项3、slave2~my.cnf修改项5、重启mys…

我的网络安全之路——一场诗意的邂逅

文章来源&#xff5c;MS08067 安全实验室 本文作者&#xff1a;tuooo 我的网络安全之路 一场诗意的邂逅 童年的星光中&#xff0c;我仰望着璀璨的荧屏&#xff0c;心怀对未知机器世界的浩瀚与好奇。那时的我&#xff0c;每每想到各种游戏的破解版本与工具&#xff0c;便会被技术…