当前位置: 首页 > news >正文

用HTML和CSS打造跨年烟花秀视觉盛宴

目录

一、程序代码

二、代码原理

三、运行效果


一、程序代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>跨年烟花秀</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"><pathd="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"><pathd="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"><pathd="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"><pathd="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">惊喜即将来临!</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>AutoFire</span></label></div><div class="form-option form-option--checkbox"><label id="finale-mode-label"><input id="finale-mode" type="checkbox" /><span>FinaleMode</span></label></div><div class="form-option form-option--checkbox"><label id="hide-controls-label"><input id="hide-controls" type="checkbox" /><span>HideControls</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 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 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 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 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 updatespeedBarOpacity = 1;// If we updated the speed, return truereturn true;}// Return false if the speed wasn't 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 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);}}}});// 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 container's background-color, so it's 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 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 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 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 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 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 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>

二、代码原理

这段代码的实现原理主要是通过 HTML、CSS 和外部资源(图标、字体和样式表)来构建一个具有烟花秀效果的网页。

首先,通过<meta>标签设置文档的元数据,如字符编码、视口大小等。然后,通过<link>标签引入外部资源,包括图标、字体和样式表。

<style>标签中,定义了一系列的 CSS 样式规则。这些样式规则用于设置页面元素的位置、尺寸、颜色、字体等样式属性。例如,设置容器的高度为100%,设置背景颜色为黑色,设置文字的颜色和字体等。

在样式规则中,使用了类选择器(以.开头)和id选择器(以#开头)来选择特定的元素,并对它们应用相应的样式。例如,通过.container选择器选择了类名为.container的元素,并设置其为居中对齐。

此外,还使用了媒体查询(@media)来根据不同的屏幕宽度设置不同的样式。例如,在宽度大于800px时,显示菜单栏并设置透明度为1;在小于800px时,隐藏菜单栏。

整个页面的布局和样式通过HTML和CSS实现,通过引入外部资源来美化页面。最终效果是一个具有烟花秀效果的网页,用户可以在此网页上观看跨年烟花秀。

鉴于该代码较长,此处只对一小部分核心代码进行解释说明,具体如下所示:

  1. <!DOCTYPE html>:声明文档类型为 HTML。
  2. <html lang="en">:开始 HTML 标签,并指定页面语言为英语。
  3. <head>:头部标签,包含了关于文档的元数据。
  4. <meta charset="UTF-8">:设置字符编码为 UTF-8。
  5. <title>跨年烟花秀</title>:设置页面标题为“跨年烟花秀”。
  6. 一系列 <meta> 标签:设置视口、苹果设备支持、主题色等元信息。
  7. 一系列 <link> 标签:引入图标、字体和样式表等外部资源。
  8. <style>:内部样式表,定义页面元素的样式。
  9. CSS样式规则:包括元素的位置、尺寸、颜色、字体等样式设置。
  10. * {...}:通配符样式,设置所有元素的默认样式。
  11. .container {...}:定义类名为.container的容器样式。
  12. #loading-init {...}:定义id为loading-init的元素样式。
  13. #stage-container {...}:定义id为stage-container的元素样式。
  14. #canvas-container {...}:定义id为canvas-container的元素样式。
  15. #controls {...}:定义id为controls的元素样式。
  16. 媒体查询 @media (min-width: 800px):针对不同屏幕宽度设置不同的样式。
  17. #menu {...}:定义id为menu的元素样式。
  18. #menu__header {...}:定义id为menu__header的元素样式。
  19. #menu form {...}:定义form元素的样式。
  20. #menu .form-option {...}:定义类名为.form-option的表单选项样式。
  21. #menu .form-option label {...}:定义表单选项中的标签样式。
  22. #menu .form-option--select {...}:定义下拉选择框的样式。
  23. #menu .form-option--checkbox {...}:定义复选框的样式。
  24. 媒体查询 @media (max-width: 800px):针对小屏幕设备设置不同的样式。
  25. #close-menu-btn {...}:定义关闭菜单按钮样式。
  26. .btn {...}:定义按钮样式。
  27. .btn svg {...}:定义按钮内的 SVG 图标样式。

三、运行效果

相关文章:

用HTML和CSS打造跨年烟花秀视觉盛宴

目录 一、程序代码 二、代码原理 三、运行效果 一、程序代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>跨年烟花秀</title><meta name"viewport" content"widthdevi…...

SSH密钥认证登陆流程(Vscode连接到远程)

目录 前言连接远程步骤1. 下载工具包wsCli到本地机器2. 本地机器上生成ssh密钥3. 在服务器上安装公钥4. vscode连接到远程 参考资料 前言 SSH&#xff08;Secure Shell&#xff09;是一种用于远程登录和安全传输数据的网络协议。它提供了两种主要的远程连接方式&#xff1a; 密…...

k8s进阶之路-pod探针:容器内的监控机制,检测应用是否存活

配合Pod重启策略&#xff1a; RestartPolicy 应用程序是否启动完成&#xff1a; 3.startupProbe启动探针1.16&#xff1a;排他性 如果三个都配置了&#xff0c;会优先启动&#xff0c;会禁用前两个反馈成功后&#xff0c;才会启动以下两个 应用启动成功后&#xff1a; 1.L…...

2.1.1 摄像头

摄像头 更多内容&#xff0c;请关注&#xff1a; github&#xff1a;https://github.com/gotonote/Autopilot-Notes.git 摄像头是目前自动驾驶车中应用和研究最广泛的传感器&#xff0c;其采集图像的过程最接近人类视觉系统。基于图像的物体检测和识别技术已经相当成熟&#…...

linux安装mysql8且初始化表名忽略大小写

mysql8下载地址 MySQL8.0安装步骤 1、把安装包上传到linux系统&#xff0c;解压、重命名并移动到/usr/local/目录&#xff1a; cd ~ tar -xvf mysql-8.0.32-linux-glibc2.12-x86_64.tar.xz mv mysql-8.0.32-linux-glibc2.12-x86_64/ mysql80/ mv mysql80/ /usr/local/2、在M…...

Java-长字符串加密

引言&#xff1a; 在数据安全领域&#xff0c;加密技术是保护信息不被未授权访问的重要手段。特别是在处理长字符串时&#xff0c;如何保证加密后的数据既安全又高效&#xff0c;是一个值得探讨的话题。本文将介绍几种常见的加密算法&#xff0c;并展示如何在Java中实现这些算法…...

使用pytest单元测试框架执行单元测试

Pytest 是一个功能强大且灵活的 Python 单元测试框架&#xff0c;它使编写、组织和运行测试变得更加简单。以下是 Pytest 的一些主要特点和优点&#xff1a; 简单易用&#xff1a;Pytest 提供了简洁而直观的语法&#xff0c;使编写测试用例变得非常容易。它支持使用 assert 语…...

Flutter 中 DraggableScrollableSheet 的属性介绍与使用

在 Flutter 中&#xff0c;DraggableScrollableSheet 是一个非常有用的小部件&#xff0c;它允许用户通过手势来拖动一个可滚动的区域&#xff0c;通常被用作底部弹出式面板或者随手势拖动的控件。本文将介绍 DraggableScrollableSheet 的属性以及如何在 Flutter 中使用它。 D…...

分库分表面试必背

一&#xff0c;背景 随着互联网的普及&#xff0c;使用人数和场景爆炸式增长&#xff0c;现在随便一个应用系统都可能达到数百万千万甚至更大数量级的数据。大量的数据带来了新的挑战&#xff0c;怎么快速完成增删改查的业务&#xff0c;是应用服务开发者最头痛的问题。面对这个…...

14个常见的Java课程设计/毕业设计合集(源码+文档)

从网上整理收集了14个常见的java系统设计源码&#xff0c;可以用于课程作业或者毕业设计。 1.基于java的家政预约网站系统 平台采用B/S结构&#xff0c;后端采用主流的Springboot框架进行开发&#xff0c;前端采用主流的Vue.js进行开发。 整个平台包括前台和后台两个部分。 …...

如何用 docker 部署程序?

如何用 docker 部署程序&#xff1f;这个问题有点笼统。 如果是MySQL、Redis这些&#xff0c;只需要拉取镜像&#xff0c;然后设置必要的配置&#xff0c;最终创建并运行实例即可。 如果你的应用是一个Java应用程序&#xff0c;使用Docker来部署它会涉及到Java特有的一些考虑…...

5G固定无线接入(FWA)

固定无线接入&#xff08;FWA&#xff09; 固定无线接入&#xff08;Fixed Wireless Access&#xff09;是使用两个固定点之间的无线电链路提供无线宽带的过程。换句话说&#xff0c;固定无线是一种为家庭或企业提供无线互联网接入的方式&#xff0c;无需铺设光纤和电缆来提供最…...

Unity ScreenPointToRay 获取到的坐标不准确

&#x1f47e;奇奇怪怪的 &#x1f959;问题描述&#x1f96a;解决方案&#x1f37f;验证代码 &#x1f959;问题描述 使用&#xff1a;Camera.main.ScreenPointToRay 将鼠标坐标转换成射线&#xff0c;然后通过&#xff1a;Physics.Raycast 获取到射线碰撞到的坐标&#xff0…...

AJAXJSON入门篇

AJAX&JSON 概念&#xff1a;AJAX(Asynchronous JavaScript And XML):异步的JavaScript和XML AJAX作用&#xff1a; 与服务器进行数据交换&#xff1a;通过AJAX可以给服务器发送请求&#xff0c;并获取服务器响应的数据 使用了AJAX和服务器进行通信&#xff0c;就可以使用H…...

代码随想录算法训练营29期|day54 任务以及具体安排

第九章 动态规划part11 123.买卖股票的最佳时机III // 版本一 class Solution {public int maxProfit(int[] prices) {int len prices.length;// 边界判断, 题目中 length > 1, 所以可省去if (prices.length 0) return 0;/** 定义 5 种状态:* 0: 没有操作, 1: 第一次买入…...

文件操作相关工具类

目录 1. 文件上传工具类 -- FileUploadUtils 2. 文件处理工具类 -- FileUtils 3. 媒体类型工具类 -- MimeTypeUtils 1. 文件上传工具类 -- FileUploadUtils /*** 文件上传工具类**/ public class FileUploadUtils {private static final Logger log LoggerFactory.ge…...

Spring源码:手写SpringIOC

文章目录 一、分析二、实现1、版本1&#xff1a;实现Bean注入IOC容器&#xff0c;并从容器中获取1&#xff09;定义BeanDefinition2&#xff09;定义BeanDefinition实现类3&#xff09;定义BeanDefinitionRegistry4&#xff09;定义Beanfactory5&#xff09;定义默认Beanfactor…...

【软件设计师】程序猿需掌握的技能——数据流图

作为一个程序员&#xff0c;不仅要具备高水平的程序编码能力&#xff0c;还要是熟练掌握软件设计的方法和技术&#xff0c;具有一定的软件设计能力&#xff0c;一般包括软件分析设计图&#xff08;常见的有数据流图&#xff0c;程序流程图&#xff0c;系统流程图&#xff0c;E-…...

17.3.1 像素处理

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.3.1 像素处理 C#处理图像&#xff0c;主要使用到Bitmap 类的 GetPixel方法和SetPixel方法。 Bitmap.GetPixel 方法&#xff1a…...

白话微机:8.解释FPGA以及一些考研面试问题

一. 前言&#xff08;更新世界观&#xff09; 在“微机世界”&#xff0c;普通的城市(单片机)里&#xff0c;人又有一个别的名字叫做“数据”&#xff0c;人有0有1&#xff1b;人们也有住房&#xff0c;这些住房在这个世界叫做“存储器”&#xff1b;地上有路&#xff0c;这些路…...

Go 语言接口详解

Go 语言接口详解 核心概念 接口定义 在 Go 语言中&#xff0c;接口是一种抽象类型&#xff0c;它定义了一组方法的集合&#xff1a; // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的&#xff1a; // 矩形结构体…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)

引言&#xff1a;为什么 Eureka 依然是存量系统的核心&#xff1f; 尽管 Nacos 等新注册中心崛起&#xff0c;但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制&#xff0c;是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)

目录 一、&#x1f44b;&#x1f3fb;前言 二、&#x1f608;sinx波动的基本原理 三、&#x1f608;波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、&#x1f30a;波动优化…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

C++.OpenGL (14/64)多光源(Multiple Lights)

多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

群晖NAS如何在虚拟机创建飞牛NAS

套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...