用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 图标样式。

三、运行效果

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

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

相关文章

HCIA-HarmonyOS设备开发认证V2.0-轻量系统内核基础-信号量semaphore

目录 一、信号量基本概念二、信号量运行机制三、信号量开发流程四、信号量接口五、代码分析&#xff08;待续...&#xff09;坚持就有收获 一、信号量基本概念 信号量&#xff08;Semaphore&#xff09;是一种实现任务间通信的机制&#xff0c;可以实现任务间同步或共享资源的…

[02] Vue指令(1)

目录 Vue中的常用指令内容渲染指令条件渲染指令事件绑定指令内联语句事件处理函数调用传参 Vue中的常用指令 概念&#xff1a;指令&#xff08;Directives&#xff09;是Vue提供的带有v- 前缀的特殊标签属性。 vue 中的指令按照不同的用途可以分为如下 6 大类&#xff1a; 内容…

Ubuntu学习笔记-Ubuntu搭建禅道开源版及基本使用

文章目录 概述一、Ubuntu中安装1.1 复制下载安装包路径1.2 将安装包解压到ubuntu中1.3 启动服务1.4 设置开机自启动 二、禅道服务基本操作2.1 启动&#xff0c;停止&#xff0c;重启&#xff0c;查看服务状态2.2 开放端口2.3 访问和登录禅道 卜相机关 卜三命、相万生&#xff0…

算法学习——LeetCode力扣二叉树篇7

算法学习——LeetCode力扣二叉树篇7 236. 二叉树的最近公共祖先 236. 二叉树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; 描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点…

揭开Markdown的秘籍:引用|代码块|超链接

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;Markdown指南、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. ⛳️Markdown 引用1.1 &#x1f514;引用1.2 &#x1f514;嵌套引用1.3 &…

中科院一区论文复现,改进蜣螂算法,Fuch映射+反向学习+自适应步长+随机差分变异,MATLAB代码...

本期文章复现一篇发表于2024年来自中科院一区TOP顶刊《Energy》的改进蜣螂算法。 论文引用如下&#xff1a; Li Y, Sun K, Yao Q, et al. A dual-optimization wind speed forecasting model based on deep learning and improved dung beetle optimization algorithm[J]. Ener…

php基础学习之可变函数(web渗透测试关键字绕过rce和回调函数)

可变函数 看可变函数的知识点之前&#xff0c;蒟蒻博主建议你先去看看php的可变变量&#xff0c;会更加方便理解&#xff0c;在本篇博客中的第五块知识点->php基础学习之变量-CSDN博客 描述 当一个变量所保存的值刚好是一个函数的名字&#xff08;由函数命名规则可知该值必…

anomalib1.0学习纪实-续1:增加新算法

0、基本信息 现在我要增加一个新算法&#xff1a;DDAD 他的代码&#xff0c;可以在github中找到&#xff1a;GitHub - arimousa/DDAD 一、基础操作&#xff1a; 1、修改anomalib\src\anomalib\models\__init__.py 我增加的第33行和61行&#xff0c; 2、 增加ddad文件夹和文…

Sora和Pika,RunwayMl,Stable Video对比!网友:Sora真王者,其他都是弟

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

《区块链公链数据分析简易速速上手小册》第1章:区块链基础(2024 最新版)

文章目录 1.1 区块链技术概览&#xff1a;深入探究与实用案例1.1.1 区块链的核心概念1.1.2 重点案例&#xff1a;供应链管理1.1.3 拓展案例 1&#xff1a;数字身份验证1.1.4 拓展案例 2&#xff1a;智能合约在房地产交易中的应用 1.2 主流公链介绍1.2.1 公链的核心概念1.2.2 重…

Sora:将文本转化为视频的创新之旅

一.能力 我们正致力于让 AI 掌握理解和模拟物理世界动态的能力&#xff0c;旨在培养能够协助人们解决现实世界互动问题的模型。 介绍 Sora——我们开发的文本到视频转换模型。Sora 能够根据用户的输入提示&#xff0c;生成最长达一分钟的高质量视频内容。 目前&#xff0c;Sora…

配置oracle连接管理器(cman)

Oracle Connection Manager是一个软件组件&#xff0c;可以在oracle客户端上指定安装这个组件&#xff0c;Oracle连接管理器代理发送给数据库服务器的请求&#xff0c;在连接管理器中&#xff0c;我们可以通过配置各种规则来控制会话访问。 简而言之&#xff0c;不同于专用连接…

Qt for android : Qt6.6.2 搭建 环境

环境说明 参考Qt助手: Assistant 6.6.2 (MinGW 11.2.0 64-bit) ***Gradle : Gradle wrapper, version 8.3***JDK11 SDK Tools / NDK 25.1.8937393 参考 Qt For Android : Qt5.13.1 Qt for android: Qt6.4搭建环境遇到的几个问题

树莓派5 EEPROM引导加载程序恢复镜像

树莓派5不能正常启动&#xff0c;可以通过电源led灯的闪码来判断错误发生的大致情形。 LED警告闪码 如果树莓派由于某种原因无法启动&#xff0c;或者不得不关闭&#xff0c;在许多情况下&#xff0c;LED会闪烁特定的次数来指示发生了什么。LED会闪烁几次长闪烁&#xff0c;然…

TIM输出比较 P2

D触发器&#xff1f; 一、输出比较 二、PWM 1、简介 2、结构 三、外部设备 1.舵机 2.直流电机 我的理解是xO1 xIN1 & PWMx; xO2 xIN2 & PWMx;引入PWMx可以更方便的控制特定的电路。 四、函数学习 /*****单独设置输出比较极性*****/ void TIM_OC1PolarityConfig(…

C++中对象的构造与析构顺序

一、对象的构造顺序 对象的构造&#xff0c;先被创建的对象&#xff0c;先被构造&#xff0c;先调用其构造函数 class A { private:int _a 0; public://构造函数A(int a 0){_a a;cout << "A(int a 0)" << " " << _a << endl…

QPaint绘制自定义坐标轴组件00

最终效果 1.创建一个ui页面&#xff0c;修改背景颜色 鼠标右键->改变样式表->添加颜色->background-color->选择合适的颜色->ok->Apply->ok 重新运行就可以看到widget的背景颜色已经改好 2.创建一个自定义的widget窗口小部件类&#xff0c;class MyChart…

个人 AI 的革命:Nvidia‘s Chat with RTX 深度探索

个人 AI 的革命&#xff1a;Nvidias Chat with RTX 深度探索 Nvidia 推出的 Chat with RTX 预示着个人 AI 新时代的到来。2 月 13 日&#xff0c;Nvidia 官宣了自家的 AI 聊天机器人&#xff0c;这不仅是人工智能交互的渐进式改进&#xff1b;更代表了个人如何利用自己的数据进…

MySQL 基础知识(八)之用户权限管理

目录 1 MySQL 权限管理概念 2 用户管理 2.1 创建用户 2.2 查看当前登录用户 2.3 修改用户名 2.4 删除用户 3 授予权限 3.1 授予用户管理员权限 3.2 授予用户数据库权限 3.3 授予用户表权限 3.4 授予用户列权限 4 查询权限 5 回收权限 1 MySQL 权限管理概念 关于 M…

用tensorflow模仿BP神经网络执行过程

文章目录 用矩阵运算仿真BP神经网络y relu ( (X․W ) b )y sigmoid ( (X․W ) b ) 以随机数产生Weight(W)与bais(b)placeholder 建立layer函数改进layer函数&#xff0c;使其能返回w和b github地址https://github.com/fz861062923/TensorFlow 用矩阵运算仿真BP神经网络 impo…