新年烟花代码
效果展示
代码
< ! 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 ;
const MAX_WIDTH = 7680 ;
const MAX_HEIGHT = 4320 ;
const GRAVITY = 0.9 ;
let simSpeed = 1 ;
const COLOR = {
Red : '#ff0043' ,
Green : '#14fc56' ,
Blue : '#1e7fff' ,
Purple : '#e60aff' ,
Gold : '#ffae00' ,
White : '#ffffff'
} ;
const INVISIBLE = '_INVISIBLE_' ;
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 ( ) {
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 ( ) ;
}
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)
} ) ;
}
const canInteract = ( ) => ! store. state. paused && ! store. state. menuOpen;
const shellNameSelector = ( ) => store. state. config. shell;
const shellSizeSelector = ( ) => + store. state. config. size;
const finaleSelector = ( ) => store. state. config. finale;
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'
} ;
Object. keys ( appNodes) . forEach ( key => {
appNodes[ key] = document. querySelector ( appNodes[ key] ) ;
} ) ;
document. getElementById ( 'loading-init' ) . remove ( ) ;
appNodes. stageContainer. classList. remove ( 'remove' ) ;
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 ) ) ;
const COLOR_NAMES = Object. keys ( COLOR ) ;
const COLOR_CODES = COLOR_NAMES . map ( colorName => COLOR [ colorName] ) ;
const COLOR_CODES_W_INVIS = [ ... COLOR_CODES , INVISIBLE ] ;
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 ) ,
} ;
} ) ;
function randomColorSimple ( ) {
return COLOR_CODES [ Math. random ( ) * COLOR_CODES . length | 0 ] ;
}
let lastColor;
function randomColor ( options ) {
const notSame = options && options. notSame;
const notColor = options && options. notColor;
const limitWhite = options && options. limitWhite;
let color = randomColorSimple ( ) ;
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
] ;
trailsStage. ctx. fillStyle = '#000' ;
trailsStage. ctx. fillRect ( 0 , 0 , trailsStage. width, trailsStage. height) ;
function requestFullscreen ( ) {
if ( fullscreenEnabled ( ) && ! isFullscreen ( ) ) {
fscreen. requestFullscreen ( document. documentElement) ;
}
}
function fullscreenEnabled ( ) {
return fscreen. fullscreenEnabled;
}
function isFullscreen ( ) {
return ! ! fscreen. fullscreenElement;
}
function makePistilColor ( shellColor ) {
return ( shellColor === COLOR . White || shellColor === COLOR . Gold) ? randomColor ( { notColor : shellColor } ) : whiteOrGold ( ) ;
}
const crysanthemumShell = ( size= 1 ) => {
const glitter = Math. random ( ) < 0.25 ;
const singleColor = Math. random ( ) < 0.68 ;
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
} ) ;
const crackleShell = ( size= 1 ) => {
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) ;
}
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 init ( ) {
let options = '' ;
shellNames. forEach ( opt => options += ` ${ opt} "> ${ opt} ` ) ;
appNodes. shellType. innerHTML = options;
options = '' ;
[ '3"' , '5"' , '6"' , '8"' , '12"' ] . forEach ( ( opt, i ) => options += ` ${ i} "> ${ opt} ` ) ;
appNodes. shellSize. innerHTML = options;
renderApp ( store. state) ;
}
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)
} ;
}
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 ( )
) ;
}
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 ;
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 ) {
if ( event. keyCode === 80 ) {
togglePause ( ) ;
}
else if ( event. keyCode === 79 ) {
toggleMenu ( ) ;
}
else if ( event. keyCode === 27 ) {
toggleMenu ( false ) ;
}
}
mainStage. addEventListener ( 'pointerstart' , handlePointerStart) ;
mainStage. addEventListener ( 'pointerend' , handlePointerEnd) ;
mainStage. addEventListener ( 'pointermove' , handlePointerMove) ;
window. addEventListener ( 'keydown' , handleKeydown) ;
window. addEventListener ( 'touchend' , ( event ) => ! IS_DESKTOP && requestFullscreen ( ) ) ;
function handleResize ( ) {
const w = window. innerWidth;
const h = window. innerHeight;
const containerW = Math. min ( w, MAX_WIDTH ) ;
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) ) ;
}
handleResize ( ) ;
window. addEventListener ( 'resize' , handleResize) ;
let speedBarOpacity = 0 ;
let autoLaunchTime = 0 ;
function updateSpeedFromEvent ( event ) {
if ( isUpdatingSpeed || event. y >= mainStage. height - 44 ) {
const edge = 16 ;
const newSpeed = ( event. x - edge) / ( mainStage. width - edge * 2 ) ;
simSpeed = Math. min ( Math. max ( newSpeed, 0 ) , 1 ) ;
speedBarOpacity = 1 ;
return true ;
}
return false ;
}
function updateGlobals ( timeStep, lag ) {
if ( ! isUpdatingSpeed) {
speedBarOpacity -= lag / 30 ;
if ( speedBarOpacity < 0 ) {
speedBarOpacity = 0 ;
}
}
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 => {
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;
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
) ;
}
}
}
} ) ;
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) ;
trailsCtx. globalCompositeOperation = 'lighten' ;
mainCtx. clearRect ( 0 , 0 , width, height) ;
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) ;
}
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 ( ) ;
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 ( ) ;
} ) ;
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 ( ) ;
}
const currentSkyColor = { r : 0 , g : 0 , b : 0 } ;
const targetSkyColor = { r : 0 , g : 0 , b : 0 } ;
function colorSky ( speed ) {
const maxSkySaturation = 30 ;
const maxStarCount = 500 ;
let totalStarCount = 0 ;
targetSkyColor. r = 0 ;
targetSkyColor. g = 0 ;
targetSkyColor. b = 0 ;
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;
} ) ;
const intensity = Math. pow ( Math. min ( 1 , totalStarCount / maxStarCount) , 0.3 ) ;
const maxColorComponent = Math. max ( 1 , targetSkyColor. r, targetSkyColor. g, targetSkyColor. b) ;
targetSkyColor. r = targetSkyColor. r / maxColorComponent * maxSkySaturation * intensity;
targetSkyColor. g = targetSkyColor. g / maxColorComponent * maxSkySaturation * intensity;
targetSkyColor. b = targetSkyColor. b / maxColorComponent * maxSkySaturation * intensity;
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) ;
function createParticleArc ( start, arcLength, count, randomness, particleFactory ) {
const angleDelta = arcLength / count;
const end = start + arcLength - ( angleDelta * 0.5 ) ;
if ( end > start) {
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) ;
}
}
}
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
) ;
} ) ;
}
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,
Math. pow ( Math. random ( ) , 0.45 ) * 2.4 ,
1000 + Math. random ( ) * 300 ,
star. speedX,
star. speedY
) ;
} ) ;
BurstFlash. add ( star. x, star. y, 24 ) ;
}
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,
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 ;
} ) ;
BurstFlash. add ( star. x, star. y, 24 ) ;
}
function crackleEffect ( star ) {
createParticleArc ( 0 , PI_2 , 10 , 1.8 , ( angle ) => {
Spark. add (
star. x,
star. y,
COLOR . Gold,
angle,
Math. pow ( Math. random ( ) , 0.45 ) * 2.4 ,
300 + Math. random ( ) * 200
) ;
} ) ;
}
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;
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;
const hpad = 60 ;
const vpad = 50 ;
const minHeightPercent = 0.45 ;
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;
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 ) ,
launchVelocity * ( this . horsetail ? 100 : 400 )
) ;
comet. heavy = true ;
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) ;
}
burst ( x, y ) {
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,
Math. pow ( Math. random ( ) , 0.45 ) * speed,
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 ;
} else {
color = this . color;
}
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 => {
const initSpeedX = Math. sin ( angle) * speed * ringSquash;
const initSpeedY = Math. cos ( angle) * speed;
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,
newSpeed,
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;
}
} ) ;
}
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) ;
}
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) ;
}
} ;
function createParticleCollection ( ) {
const collection = { } ;
COLOR_CODES_W_INVIS . forEach ( color => {
collection[ color] = [ ] ;
} ) ;
return collection;
}
const Star = {
drawWidth : 3 ,
airDrag : 0.98 ,
airDragHeavy : 0.992 ,
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 ;
instance. sparkSpeed = 1 ;
instance. sparkTimer = 0 ;
instance. sparkColor = color;
instance. sparkLife = 750 ;
instance. sparkLifeVariation = 0.25 ;
this . active[ color] . push ( instance) ;
return instance;
} ,
returnInstance ( instance ) {
instance. onDeath && instance. onDeath ( instance) ;
instance. onDeath = null ;
this . _pool. push ( instance) ;
}
} ;
const Spark = {
drawWidth : 0.75 ,
airDrag : 0.9 ,
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;
} ,
returnInstance ( instance ) {
this . _pool. push ( instance) ;
}
} ;
init ( ) ;
< / script>
< / body>
< / html>