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

news2024/11/25 11:27:19

在浏览器搜索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>

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大模型应用:LangChain-Golang核心模块使用

1.简介 LangChain是一个开源的框架&#xff0c;它提供了构建基于大模型的AI应用所需的模块和工具。它可以帮助开发者轻松地与大型语言模型(LLM)集成&#xff0c;实现文本生成、问答、翻译、对话等任务。LangChain的出现大大降低了AI应用开发的门槛&#xff0c;使得任何人都可以…

Vue引入element-plus-04

我们这次开发是使用vue的脚手架来进行开发,前面我们已经使用过最原生的方式去编写我们的vue的语法,从今天开始就使用vue的脚手架,但是前提是你需要用于node的环境 在我们开始之前&#xff0c;我们至少需要有node npm是什么&#xff1f; npm是一个强大的包管理工具&#xff0c;它…

监控神器vnStat初探

文章目录 一、概述二、官方docker部署1. vnStat守护进程和HTTP服务器在同一容器中运行2. 双容器运行&#xff0c;vnstat容器收集数据&#xff0c;vnstati容器提供web服务 三、修改后的编排文件四、运行结果五、停止监控不感兴趣的网卡 一、概述 vnStat是一款网络流量监测工具&…

智慧监狱技术解决方案

1. **建设背景**&#xff1a;介绍了智慧监狱建设的战略部署&#xff0c;包括司法部提出的“数字法治、智慧司法”信息化体系建设&#xff0c;以及智慧监狱建设的总体目标、重点任务和实施步骤。 2. **建设需求**&#xff1a;分析了当前监狱系统存在的问题&#xff0c;如子系统…

Java新特性与性能调优

引言 Java不断演进&#xff0c;每个新版本都引入了新的特性和改进&#xff0c;帮助开发者在提高生产力的同时&#xff0c;也能更好地优化程序性能。本文将详细介绍Java新版本中的重要特性&#xff0c;如从Java 8到Java 17&#xff0c;并探讨性能调优的方法&#xff0c;包括JVM调…

沃尔玛自养号测评:优势与技术要求解析

沃尔玛自养号测评是一种卖家在沃尔玛平台上提升店铺权重和排名的营销手段。传统运营策略的局限性日益显现&#xff0c;如营销手段单一、难以应对市场竞争等。因此&#xff0c;许多卖家为了提升店铺权重和排名&#xff0c;选择了自养号测评这一技术手段。 以下是对沃尔玛自养号…

C++ 46 之 关系运算符的重载

#include <iostream> #include <string> using namespace std;class Students06{ public:string m_name;int m_age;Students06(string name, int age){this->m_name name;this->m_age age;}// 重载了 bool operator(Students06 &stu){if(this->m_na…

CNN学习(7):用C++实现简单不同参数的卷积模型

目录 一、参数说明和计算公式 1、符号约定 2、输出大小计算公式 二、不同类型的卷积 1、输入3*3*1&#xff0c;卷积核3*3*1&#xff0c;输出1*1*1 &#xff08;1&#xff09;实现代码 &#xff08;2&#xff09;代码说明 2、输入4*4*1&#xff0c;卷积核3*3*1&#xff…

如何避免销售飞单私单!教你如何巧妙避开陷阱,业绩飙升!

明明投入了大量的时间和精力&#xff0c;客户却悄无声息地消失了&#xff1f;或是突然有一天&#xff0c;你发现原本属于你的订单被同事悄悄抢走&#xff1f;这背后&#xff0c;很可能隐藏着销售飞单私单的陷阱。今天&#xff0c;就让我们一起探讨如何巧妙避开这些陷阱&#xf…

MySQL-----InnoDB的自适应哈希索引

InnoDB存储引擎监测到同样的二级索引不断被使用&#xff0c;那么它会根据这个二级索引&#xff0c;在内存上根据二级索引树(B树)上的二级索引值&#xff0c;在内存上构建一个哈希索引&#xff0c;来加速搜索。 查看是否开启自适应哈希索引 show variables like innodb_adapti…

2024年【安全生产监管人员】试题及解析及安全生产监管人员考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全生产监管人员试题及解析是安全生产模拟考试一点通总题库中生成的一套安全生产监管人员考试试题&#xff0c;安全生产模拟考试一点通上安全生产监管人员作业手机同步练习。2024年【安全生产监管人员】试题及解析及…

LLM资料大全:文本多模态大模型、垂直领域微调模型、STF数据集、训练微调部署框架、提示词工程等

前言 自ChatGPT为代表的大语言模型&#xff08;Large Language Model, LLM&#xff09;出现以后&#xff0c;由于其惊人的类通用人工智能&#xff08;AGI&#xff09;的能力&#xff0c;掀起了新一轮[自然语言处理]领域的研究和应用的浪潮。尤其是以ChatGLM、LLaMA等平民玩家都…

Qwen-Agent:Qwen2加持,强大的多代理框架 - 函数调用、代码解释器以及 RAG!

✨点击这里✨&#xff1a;&#x1f680;原文链接&#xff1a;&#xff08;更好排版、视频播放、社群交流、最新AI开源项目、AI工具分享都在这个公众号&#xff01;&#xff09; Qwen-Agent&#xff1a;Qwen2加持&#xff0c;强大的多代理框架 - 函数调用、代码解释器以及 RAG&…

全局解决SpringBoot框架中的application.properties/yml注解中文乱码问题(一劳永逸)

问题原因 Spring Boot在加载application.properties/yml配置文件时&#xff0c;默认使用ISO-8859-1编码。这种编码方式并不支持中文字符&#xff0c;因此当配置文件中包含中文字符时&#xff0c;就会出现乱码&#xff0c;现象如下&#xff1a; 问题解决 本解决方法是全局设置…

最快安装zabbix

部署zabbix 6.x 建议使用红帽系统。 https://download.rockylinux.org/pub/rocky/8/isos/x86_64/Rocky-8.9-x86_64-minimal.iso1> 配置安装yum源 [rootzabbix ~]# yum install https://mirrors.huaweicloud.com/zabbix/zabbix/6.2/rhel/8/x86_64/zabbix-release-6.2-3.el8…

PMS助力制造企业高效运营︱PMO大会

全国PMO专业人士年度盛会 北京易贝恩项目管理科技有限公司副总经理朱洪泽女士受邀为PMO评论主办的2024第十三届中国PMO大会演讲嘉宾&#xff0c;演讲议题为“PMS助力制造企业高效运营”。大会将于6月29-30日在北京举办&#xff0c;敬请关注&#xff01; 议题简要&#xff1a; …

DeepDriving | CUDA编程-05:流和事件

本文来源公众号“DeepDriving”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;CUDA编程-05&#xff1a;流和事件 1 CUDA流 在CUDA中有两个级别的并发&#xff1a;内核级并发和网格级并发。前面的文章DeepDriving | CUDA编程-04&…

基于System-Verilog点亮LED灯

文章目录 一、System-Verilog介绍1.1System-Verilog 二、简单的语法介绍2.1接口实例2.2全局声明和语句实例2.3时间单位和精度2.4用户定义的类型2.5 枚举类型 三、流水灯参考 一、System-Verilog介绍 1.1System-Verilog SystemVerilog是一种硬件描述和验证语言&#xff08;HDV…

数据分析必备:一步步教你如何用matplotlib做数据可视化(2)

1、Matplotlib Anaconda Anaconda是Python和R编程语言的免费开源发行版&#xff0c;用于大规模数据处理&#xff0c;预测分析和科学计算。 该分发使包管理和部署变得简单容易。 Matplotlib和许多其他有用的(数据)科学工具构成了分发的一部分。 包版本由包管理系统Conda管理。 …

50【Aseprite 作图】模糊工具 笔刷

1 模糊工具 2 笔刷 然后 选中 后 Ctrl B&#xff0c;就变成笔刷了 可以按住shift &#xff0c;像画一条线一样 或者用矩形、圆形工具、油漆桶工具 在上方可以选择笔刷的不同形式&#xff0c;如果是“图案与来源对齐”&#xff0c;就是来源不变&#xff0c;笔刷不会覆盖之前…