新钥匙建站,福建亨利建设集团有限公司网站,湖南建设人力资源网站,深圳市光明区文章目录 #x1f412;个人主页#x1f3c5;Vue项目常用组件模板仓库#x1f4d6;前言#xff1a;#x1f380;源码如下#xff1a; #x1f412;个人主页 #x1f3c5;Vue项目常用组件模板仓库 #x1f4d6;前言#xff1a;
本篇博客主要提供前端背景收集之烟花背景… 文章目录 个人主页Vue项目常用组件模板仓库前言源码如下 个人主页 Vue项目常用组件模板仓库 前言
本篇博客主要提供前端背景收集之烟花背景组件源码需要的朋友请自取 源码如下
!DOCTYPE html
html langenheadmeta charsetUTF-8title2024新年快乐万事如意/titlemeta nameviewport contentwidthdevice-width, initial-scale1, user-scalablenometa namemobile-web-app-capable contentyesmeta nameapple-mobile-web-app-capable contentyesmeta nametheme-color content#000000link relshortcut icon typeimage/pnghrefhttps://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.pnglink relicon typeimage/pnghrefhttps://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.pnglink relapple-touch-icon-precomposedhrefhttps://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.pngmeta namemsapplication-TileColor content#000000meta namemsapplication-TileImagecontenthttps://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon.pnglink hrefhttps://fonts.googleapis.com/css?familyRussoOne relstylesheetlink relstylesheet hrefhttps://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.csslink relstylesheet href./style.cssstyle* {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/headbody!-- partial:index.partial.html --!-- SVG Spritesheet --div styleheight: 0; width: 0; position: absolute; visibility: hidden;svg xmlnshttp://www.w3.org/2000/svgsymbol idicon-play viewBox0 0 24 24path dM8 5v14l11-7z //symbolsymbol idicon-pause viewBox0 0 24 24path dM6 19h4V5H6v14zm8-14v14h4V5h-4z //symbolsymbol idicon-close viewBox0 0 24 24pathdM19 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 //symbolsymbol idicon-settings viewBox0 0 24 24pathdM19.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 //symbolsymbol idicon-shutter-fast viewBox0 0 24 24pathdM21 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 //symbolsymbol idicon-shutter-slow viewBox0 0 24 24pathdM1 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 classcontainerdiv idloading-initLoading.../divdiv idstage-container classremovediv idcanvas-containercanvas idtrails-canvas/canvascanvas idmain-canvas/canvas/divdiv idcontrolsdiv idpause-btn classbtnsvg fillwhite width24 height24use href#icon-pause/use/svg/divdiv idshutter-btn classbtnsvg fillwhite width24 height24use href#icon-shutter-slow/use/svg/divdiv idsettings-btn classbtnsvg fillwhite width24 height24use href#icon-settings/use/svg/div/divdiv idmenu classhidediv idclose-menu-btn classbtn btn--brightsvg fillwhite width24 height24use href#icon-close/use/svg/divdiv idmenu__headerSettings/divformdiv classform-option form-option--selectlabelShell Type/labelselect idshell-type/select/divdiv classform-option form-option--selectlabelShell Size/labelselect idshell-size/select/divdiv classform-option form-option--checkboxlabel idauto-launch-labelinput idauto-launch typecheckbox /spanAutoFire/span/label/divdiv classform-option form-option--checkboxlabel idfinale-mode-labelinput idfinale-mode typecheckbox /spanFinaleMode/span/label/divdiv classform-option form-option--checkboxlabel idhide-controls-labelinput idhide-controls typecheckbox /spanHideControls/span/label/div/form/div/div/div!-- partial --script srchttps://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/fscreen%401.0.1.js/scriptscript srchttps://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/Stage%400.1.4.js/scriptscript srchttps://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/MyMath.js/scriptscriptuse 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 neededconst MAX_WIDTH 7680;const MAX_HEIGHT 4320;const GRAVITY 0.9; // Acceleration in px/slet 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 managementconst 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 localStorageload() {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 stateconst 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 nodesObject.keys(appNodes).forEach(key {appNodes[key] document.querySelector(appNodes[key]);});// Remove loading statedocument.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 derivationsconst COLOR_NAMES Object.keys(COLOR);const COLOR_CODES COLOR_NAMES.map(colorName COLOR[colorName]);// Invisible stars need an indentifier, even through they wont 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 randomlyif (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 prefixesfunction requestFullscreen() {if (fullscreenEnabled() !isFullscreen()) {fscreen.requestFullscreen(document.documentElement);}}function fullscreenEnabled() {return fscreen.fullscreenEnabled;}function isFullscreen() {return !!fscreen.fullscreenElement;}// Shell helpersfunction makePistilColor(shellColor) {return (shellColor COLOR.White || shellColor COLOR.Gold) ? randomColor({notColor: shellColor}) : whiteOrGold();}// Unique shell typesconst 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) {// favor goldconst 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 init() {// Populate dropdowns// shell typelet options ;shellNames.forEach(opt options option value${opt}${opt}/option);appNodes.shellType.innerHTML options;// shell sizeoptions ;[3, 5, 6, 8, 12].forEach((opt, i) options option value${i}${opt}/option);appNodes.shellSize.innerHTML options;// initial renderrenderApp(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)};}// Launches a shell from a user pointer event, based on state.configfunction 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 heightsfunction 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) {// Pif (event.keyCode 80) {togglePause();}// Oelse if (event.keyCode 79) {toggleMenu();}// Escelse 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 touchwindow.addEventListener(touchend, (event) !IS_DESKTOP requestFullscreen());function handleResize() {const w window.innerWidth;const h window.innerHeight;// Try to adopt screen size, heeding maximum sizes specifiedconst containerW Math.min(w, MAX_WIDTH);// On small screens, use full device heightconst 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 dimensionshandleResize();window.addEventListener(resize, handleResize);// Dynamic globalslet speedBarOpacity 0;let autoLaunchTime 0;function updateSpeedFromEvent(event) {if (isUpdatingSpeed || event.y mainStage.height - 44) {// On phones its 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 updatespeedBarOpacity 1;// If we updated the speed, return truereturn true;}// Return false if the speed wasnt updatedreturn false;}// Extracted function to keep update() optimizedfunction updateGlobals(timeStep, lag) {// Always try to fade out speed barif (!isUpdatingSpeed) {speedBarOpacity - lag / 30; // half a secondif (speedBarOpacity 0) {speedBarOpacity 0;}}// auto launch shellsif (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 {// StarsStar.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 isnt 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);}}}});// SparksSpark.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 modetrailsCtx.globalCompositeOperation lighten;mainCtx.clearRect(0, 0, width, height);// Draw queued burst flasheswhile (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 starstrailsCtx.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 sparkstrailsCtx.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 visibleif (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 containers background-color, so its behind the particlesconst 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 brightnessconst maxStarCount 500;let totalStarCount 0;// Initialize sky as blacktargetSkyColor.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 well 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 dont 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: angleangleangleDelta vs. angleangleDelta// V8 deoptimises with let compound assignmentfor (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 stars 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 shellfunction 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 renderBurstFlash.add(star.x, star.y, 24);}// Floral burst with willow starsfunction 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 renderBurstFlash.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 spheres 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 heightconst minHeightPercent 0.45;// Minimum burst height in pxconst 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 testinglaunchVelocity * (this.horsetail ? 100 : 400));// making comet heavy limits air dragcomet.heavy true;// comet spark trailcomet.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 lifethis.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 randomlyif (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 horizontallyconst initSpeedX Math.sin(angle) * speed * ringSquash;const initSpeedY Math.cos(angle) * speed;// Rotate ringconst 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 lifethis.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 burstelse {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 renderBurstFlash.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 propertiesdrawWidth: 3,airDrag: 0.98,airDragHeavy: 0.992,// Star particles will be keyed by coloractive: 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 emissionsinstance.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 upinstance.onDeath null;// Add back to the pool.this._pool.push(instance);}};const Spark {// Visual propertiesdrawWidth: 0.75,airDrag: 0.9,// Star particles will be keyed by coloractive: 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);}};init();/script/body
/html