• HTML下雪/烟花


    一、雪花

    <div id="snow">div>
    
    body {
      margin: 0;
      background: #333;
    }
    body #snow {
      height: 100vh;
      overflow: hidden;
      position: relative;
      /**
       * Defaults
      **/
      --size: 1;
      --fallDuration: 10s;
      --swayDuration: 0.8s;
      --fallSlideStrength: 0.5;
      --slideStrength: 0.5;
      --position: 0;
    }
    body #snow > div {
      position: absolute;
      top: 0;
      left: calc(var(--position) - 20%);
      width: calc(var(--size) * 15px);
      aspect-ratio: 1;
      background: radial-gradient(white, transparent 66%);
      -webkit-animation: var(--fallDuration) snowFall linear forwards;
              animation: var(--fallDuration) snowFall linear forwards;
    }
    
    @-webkit-keyframes snowFall {
      to {
        top: 100%;
        transform: translateX(calc(var(--cWidth) * var(--fallSlideStrength) / 8));
      }
    }
    
    @keyframes snowFall {
      to {
        top: 100%;
        transform: translateX(calc(var(--cWidth) * var(--fallSlideStrength) / 8));
      }
    }
    
    const maxSnowflakes = 1000,
    	snowflakes = [],
    	container = document.getElementById("snow");
    
    console.clear();
    
    let isRunning = true;
    
    const generatesnowFlake = (timeout = 0, init = false) => {
    	const duration = 3000 + Math.random() * 7000,
    		flake = document.createElement("div"),
    		id = crypto.randomUUID(),
    		delay = init ? Math.random() * duration : 0;
    	snowflakes.push(id);
    	setTimeout(() => {
    		flake.setAttribute("id", id);
    		flake.setAttribute(
    			"style",
    			`
    			animation-delay: -${delay}ms;
    			--fallDuration: ${duration}ms;
    			--fallSlideStrength: ${Math.random()};
    			
    			--size: ${Math.random() * 0.7 + 0.3};
    			--position: ${Math.random() * 120}%;
    			`
    		);
    		container.appendChild(flake);
    		setTimeout(() => {
    			const index = snowflakes.findIndex((e) => e === id);
    			snowflakes.splice(index, index);
    			container.removeChild(flake);
    		}, duration - delay);
    	}, timeout);
    };
    
    container.setAttribute("style", `--cWidth: ${container.clientWidth}px`);
    addEventListener("resize", () =>
    	container.setAttribute("style", `--cWidth: ${container.clientWidth}px`)
    );
    
    const loop = async () => {
    		while (1) {
    			await new Promise(async (resolve) => {
    				if (isRunning && snowflakes.length < maxSnowflakes && !document.hidden) {
    					requestAnimationFrame(() => {
    						generatesnowFlake(Math.random() * 50);
    						resolve();
    					});
    				} else {
    					setTimeout(resolve, 50);
    				}
    			});
    		}
    	},
    	init = () => {
    		for (let i = 0; i < (maxSnowflakes - snowflakes.length) / 2; i++) {
    			generatesnowFlake(Math.random() * 50, true);
    		}
    	};
    
    init();
    loop();
    
    document.onvisibilitychange = (e) => {
    	isRunning = !document.hidden;
    	if (isRunning) init();
    };
    

    二、烟花

    DOCTYPE html>
    <html lang="en" >
    <head>
      <meta charset="UTF-8">
      <title>CodePen - Firework Simulator v2title>
      <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-v2.png">
    <link rel="icon" type="image/png" href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon-v2.png">
    <link rel="apple-touch-icon-precomposed" href="https://s3-us-west-2.amazonaws.com/s.cdpn.io/329180/firework-burst-icon-v2.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-v2.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">
    
    
    head>
    <body>
    
    
    <div style="height: 0; width: 0; position: absolute; visibility: hidden;">
    	<svg xmlns="http://www.w3.org/2000/svg">
    		<symbol id="icon-play" viewBox="0 0 24 24">
    			<path d="M8 5v14l11-7z"/>
    		symbol>
    		<symbol id="icon-pause" viewBox="0 0 24 24">
    			<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
    		symbol>
    		<symbol id="icon-close" viewBox="0 0 24 24">
    			<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
    		symbol>
    		<symbol id="icon-settings" viewBox="0 0 24 24">
    			<path d="M19.43 12.98c.04-.32.07-.64.07-.98s-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.23-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23.09.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zM12 15.5c-1.93 0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z"/>
    		symbol>
    		<symbol id="icon-sound-on" viewBox="0 0 24 24">
    			<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
    		symbol>
    		<symbol id="icon-sound-off" viewBox="0 0 24 24">
    			<path d="M16.5 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C20.63 14.91 21 13.5 21 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
    		symbol>
    	svg>
    div>
    
    
    <div class="container">
    	<div class="loading-init">
    		<div class="loading-init__header">Loadingdiv>
    		<div class="loading-init__status">Assembling Shellsdiv>
    	div>
    	<div class="stage-container remove">
    		<div class="canvas-container">
    			<canvas id="trails-canvas">canvas>
    			<canvas id="main-canvas">canvas>
    		div>
    		<div class="controls">
    			<div class="btn pause-btn">
    				<svg fill="white" width="24" height="24"><use href="#icon-pause" xlink:href="#icon-pause">use>svg>
    			div>
    			<div class="btn sound-btn">
    				<svg fill="white" width="24" height="24"><use href="#icon-sound-off" xlink:href="#icon-sound-off">use>svg>
    			div>
    			<div class="btn settings-btn">
    				<svg fill="white" width="24" height="24"><use href="#icon-settings" xlink:href="#icon-settings">use>svg>
    			div>
    		div>
    		<div class="menu hide">
    			<div class="menu__inner-wrap">
    				<div class="btn btn--bright close-menu-btn">
    					<svg fill="white" width="24" height="24"><use href="#icon-close" xlink:href="#icon-close">use>svg>
    				div>
    				<div class="menu__header">Settingsdiv>
    				<div class="menu__subheader">For more info, click any label.div>
    				<form>
    					<div class="form-option form-option--select">
    						<label class="shell-type-label">Shell Typelabel>
    						<select class="shell-type">select>
    					div>
    					<div class="form-option form-option--select">
    						<label class="shell-size-label">Shell Sizelabel>
    						<select class="shell-size">select>
    					div>
    					<div class="form-option form-option--select">
    						<label class="quality-ui-label">Qualitylabel>
    						<select class="quality-ui">select>
    					div>
    					<div class="form-option form-option--select">
    						<label class="sky-lighting-label">Sky Lightinglabel>
    						<select class="sky-lighting">select>
    					div>
    					<div class="form-option form-option--select">
    						<label class="scaleFactor-label">Scalelabel>
    						<select class="scaleFactor">select>
    					div>
    					<div class="form-option form-option--checkbox">
    						<label class="auto-launch-label">Auto Firelabel>
    						<input class="auto-launch" type="checkbox" />
    					div>
    					<div class="form-option form-option--checkbox form-option--finale-mode">
    						<label class="finale-mode-label">Finale Modelabel>
    						<input class="finale-mode" type="checkbox" />
    					div>
    					<div class="form-option form-option--checkbox">
    						<label class="hide-controls-label">Hide Controlslabel>
    						<input class="hide-controls" type="checkbox" />
    					div>
    					<div class="form-option form-option--checkbox form-option--fullscreen">
    						<label class="fullscreen-label">Fullscreenlabel>
    						<input class="fullscreen" type="checkbox" />
    					div>
    					<div class="form-option form-option--checkbox">
    						<label class="long-exposure-label">Open Shutterlabel>
    						<input class="long-exposure" type="checkbox" />
    					div>
    				form>
    				<div class="credits">
    					Passionately built by <a href="https://cmiller.tech/" target="_blank">Caleb Millera>.
    				div>
    			div>
    		div>
    	div>
    	<div class="help-modal">
    		<div class="help-modal__overlay">div>
    		<div class="help-modal__dialog">
    			<div class="help-modal__header">div>
    			<div class="help-modal__body">div>
    			<button type="button" class="help-modal__close-btn">Closebutton>
    		div>
    	div>
    div>
    
    <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>
    
    body>
    html>
    
    * {
      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 !important;
    }
    
    .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;
      text-transform: uppercase;
    }
    .loading-init__header {
      font-size: 2.2em;
    }
    .loading-init__status {
      margin-top: 1em;
      font-size: 0.8em;
      opacity: 0.75;
    }
    
    .stage-container {
      overflow: hidden;
      box-sizing: initial;
      border: 1px solid #222;
      margin: -1px;
    }
    @media (max-width: 840px) {
      .stage-container {
        border: none;
        margin: 0;
      }
    }
    
    .canvas-container {
      width: 100%;
      height: 100%;
      transition: filter 0.3s;
    }
    .canvas-container canvas {
      position: absolute;
      mix-blend-mode: lighten;
      transform: translateZ(0);
    }
    
    .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: 840px) {
      .controls {
        visibility: visible;
      }
      .controls.hide:hover {
        opacity: 1;
      }
    }
    
    .menu {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      background-color: rgba(0, 0, 0, 0.42);
      transition: opacity 0.3s, visibility 0.3s;
    }
    .menu__inner-wrap {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      transition: opacity 0.3s;
    }
    .menu__header {
      margin-top: auto;
      margin-bottom: 8px;
      padding-top: 16px;
      font-size: 2em;
      text-transform: uppercase;
    }
    .menu__subheader {
      margin-bottom: auto;
      padding-bottom: 12px;
      font-size: 0.86em;
      opacity: 0.8;
    }
    .menu form {
      width: 100%;
      max-width: 400px;
      padding: 0 10px;
      overflow: auto;
      -webkit-overflow-scrolling: touch;
    }
    .menu .form-option {
      display: flex;
      align-items: center;
      margin: 16px 0;
      transition: opacity 0.3s;
    }
    .menu .form-option label {
      display: block;
      width: 50%;
      padding-right: 12px;
      text-align: right;
      text-transform: uppercase;
      -webkit-user-select: none;
         -moz-user-select: none;
          -ms-user-select: none;
              user-select: none;
    }
    .menu .form-option--select select {
      display: block;
      width: 50%;
      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 input {
      display: block;
      width: 26px;
      height: 26px;
      margin: 0;
      opacity: 0.5;
    }
    @media (max-width: 840px) {
      .menu .form-option select, .menu .form-option input {
        outline: none;
      }
    }
    
    .close-menu-btn {
      position: absolute;
      top: 0;
      right: 0;
    }
    
    .btn {
      opacity: 0.16;
      width: 50px;
      height: 50px;
      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: 840px) {
      .btn:hover {
        opacity: 0.32;
      }
      .btn--bright:hover {
        opacity: 0.75;
      }
    }
    .btn svg {
      display: block;
      margin: auto;
    }
    
    .credits {
      margin-top: auto;
      margin-bottom: 10px;
      padding-top: 6px;
      font-size: 0.8em;
      opacity: 0.75;
    }
    .credits a {
      color: rgba(255, 255, 255, 0.5);
      text-decoration: none;
    }
    .credits a:hover, .credits a:active {
      color: rgba(255, 255, 255, 0.75);
      text-decoration: underline;
    }
    
    .help-modal {
      display: flex;
      justify-content: center;
      align-items: center;
      position: fixed;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      visibility: hidden;
      transition-property: visibility;
      transition-duration: 0.25s;
    }
    .help-modal__overlay {
      position: absolute;
      top: 0;
      bottom: 0;
      left: 0;
      right: 0;
      opacity: 0;
      transition-property: opacity;
      transition-timing-function: ease-in;
      transition-duration: 0.25s;
    }
    .help-modal__dialog {
      display: flex;
      flex-direction: column;
      align-items: center;
      max-width: 400px;
      max-height: calc(100vh - 100px);
      margin: 10px;
      padding: 20px;
      border-radius: 0.3em;
      background-color: rgba(0, 0, 0, 0.4);
      opacity: 0;
      transform: scale(0.9, 0.9);
      transition-property: opacity, transform;
      transition-timing-function: ease-in;
      transition-duration: 0.25s;
    }
    @media (min-width: 840px) {
      .help-modal__dialog {
        font-size: 1.25rem;
        max-width: 500px;
      }
    }
    .help-modal__header {
      font-size: 1.75em;
      text-transform: uppercase;
      text-align: center;
    }
    .help-modal__body {
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
      margin: 1em 0;
      padding: 1em 0;
      border-top: 1px solid rgba(255, 255, 255, 0.25);
      border-bottom: 1px solid rgba(255, 255, 255, 0.25);
      line-height: 1.5;
      color: rgba(255, 255, 255, 0.75);
    }
    .help-modal__close-btn {
      flex-shrink: 0;
      outline: none;
      border: none;
      border-radius: 2px;
      padding: 0.25em 0.75em;
      margin-top: 0.36em;
      font-family: "Russo One", arial, sans-serif;
      font-size: 1em;
      color: rgba(255, 255, 255, 0.5);
      text-transform: uppercase;
      letter-spacing: 0.06em;
      background-color: rgba(255, 255, 255, 0.25);
      transition: color 0.3s, background-color 0.3s;
    }
    .help-modal__close-btn:hover, .help-modal__close-btn:active, .help-modal__close-btn:focus {
      color: #FFF;
      background-color: #09F;
    }
    .help-modal.active {
      visibility: visible;
      transition-duration: 0.4s;
    }
    .help-modal.active .help-modal__overlay {
      opacity: 1;
      transition-timing-function: ease-out;
      transition-duration: 0.4s;
    }
    .help-modal.active .help-modal__dialog {
      opacity: 1;
      transform: scale(1, 1);
      transition-timing-function: ease-out;
      transition-duration: 0.4s;
    }
    
    'use strict';
    console.clear();
    
    // This is a prime example of what starts out as a simple project
    // and snowballs way beyond its intended size. It's a little clunky
    // reading/working on this single file, but here it is anyways :)
    
    const IS_MOBILE = window.innerWidth <= 640;
    const IS_DESKTOP = window.innerWidth > 800;
    const IS_HEADER = IS_DESKTOP && window.innerHeight < 300;
    // Detect high end devices. This will be a moving target.
    const IS_HIGH_END_DEVICE = (() => {
    	const hwConcurrency = navigator.hardwareConcurrency;
    	if (!hwConcurrency) {
    		return false;
    	}
    	// Large screens indicate a full size computer, which often have hyper threading these days.
    	// So a quad core desktop machine has 8 cores. We'll place a higher min threshold there.
    	const minCount = window.innerWidth <= 1024 ? 4 : 8;
    	return hwConcurrency >= minCount;
    })();
    // Prevent canvases from getting too large on ridiculous screen sizes.
    // 8K - can restrict this if needed
    const MAX_WIDTH = 7680;
    const MAX_HEIGHT = 4320;
    const GRAVITY = 0.9; // Acceleration in px/s
    let simSpeed = 1;
    
    function getDefaultScaleFactor() {
    	if (IS_MOBILE) return 0.9;
    	if (IS_HEADER) return 0.75;
    	return 1;
    }
    
    // Width/height values that take scale into account.
    // USE THESE FOR DRAWING POSITIONS
    let stageW, stageH;
    
    // All quality globals will be overwritten and updated via `configDidUpdate`.
    let quality = 1;
    let isLowQuality = false;
    let isNormalQuality = true;
    let isHighQuality = false;
    
    const QUALITY_LOW = 1;
    const QUALITY_NORMAL = 2;
    const QUALITY_HIGH = 3;
    
    const SKY_LIGHT_NONE = 0;
    const SKY_LIGHT_DIM = 1;
    const SKY_LIGHT_NORMAL = 2;
    
    const COLOR = {
    	Red: '#ff0043',
    	Green: '#14fc56',
    	Blue: '#1e7fff',
    	Purple: '#e60aff',
    	Gold: '#ffbf36',
    	White: '#ffffff'
    };
    
    // Special invisible color (not rendered, and therefore not in COLOR map)
    const INVISIBLE = '_INVISIBLE_';
    
    const PI_2 = Math.PI * 2;
    const PI_HALF = Math.PI * 0.5;
    
    // Stage.disableHighDPI = true;
    const trailsStage = new Stage('trails-canvas');
    const mainStage = new Stage('main-canvas');
    const stages = [
    	trailsStage,
    	mainStage
    ];
    
    
    
    // Fullscreen helpers, using Fscreen for prefixes.
    function fullscreenEnabled() {
    	return fscreen.fullscreenEnabled;
    }
    
    // Note that fullscreen state is synced to store, and the store should be the source
    // of truth for whether the app is in fullscreen mode or not.
    function isFullscreen() {
    	return !!fscreen.fullscreenElement;
    }
    
    // Attempt to toggle fullscreen mode.
    function toggleFullscreen() {
    	if (fullscreenEnabled()) {
    		if (isFullscreen()) {
    			fscreen.exitFullscreen();
    		} else {
    			fscreen.requestFullscreen(document.documentElement);
    		}
    	}
    }
    
    // Sync fullscreen changes with store. An event listener is necessary because the user can
    // toggle fullscreen mode directly through the browser, and we want to react to that.
    fscreen.addEventListener('fullscreenchange', () => {
    	store.setState({ fullscreen: isFullscreen() });
    });
    
    
    
    
    // Simple state container; the source of truth.
    const store = {
    	_listeners: new Set(),
    	_dispatch(prevState) {
    		this._listeners.forEach(listener => listener(this.state, prevState))
    	},
    	
    	state: {
    		// will be unpaused in init()
    		paused: true,
    		soundEnabled: false,
    		menuOpen: false,
    		openHelpTopic: null,
    		fullscreen: isFullscreen(),
    		// Note that config values used for