HTML实现闪电打字效果

news2025/1/21 0:53:24

演示删

完整HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>H5 Canvas雷电打出文字特效</title>

<style>
.page-thunder-to-text {
  position: relative;
  overflow: hidden;
}
.page-thunder-to-text canvas {
  display: block;
}
.page-thunder-to-text input {
  position: absolute;
  bottom: 50px;
  left: 0;
  right: 0;
  display: block;
  outline: none;
  background-color: rgba(38, 50, 56, 0.2);
  color: #ffffff;
  border: none;
  width: 50%;
  min-width: 500px;
  max-width: 100%;
  margin: auto;
  height: 60px;
  line-height: 60px;
  font-size: 40px;
  padding: 0 20px;
}
.page-thunder-to-text input:hover, .page-thunder-to-text input:focus {
  border: 1px solid rgba(38, 50, 56, 0.6);
}
.page-thunder-to-text input::-webkit-input-placeholder {
  color: rgba(255, 255, 255, 0.1);
}
</style>

</head>
<body>

<div class="page page-thunder-to-text">
	<input id="input" type="text" maxlength="24" placeholder="I love you!">
	<canvas id="canvas"></canvas>
</div>

<script>
let canvas, ctx, w, h, thunder, text, particles, input;

function Thunder(options) {
    options = options || {};
    this.lifespan = options.lifespan || Math.round(Math.random() * 10 + 10);
    this.maxlife = this.lifespan;
    this.color = options.color || '#fefefe';
    this.glow = options.glow || '#2323fe';
    this.x = options.x || Math.random() * w;
    this.y = options.y || Math.random() * h;
    this.width = options.width || 2;
    this.direct = options.direct || Math.random() * Math.PI * 2;
    this.max = options.max || Math.round(Math.random() * 10 + 20);
    this.segments = [...new Array(this.max)].map(() => {
        return {
            direct: this.direct + (Math.PI * Math.random() * 0.2 - 0.1),
            length: Math.random() * 20 + 80,
            change: Math.random() * 0.04 - 0.02
        };
    });

    this.update = function(index, array) {
        this.segments.forEach(s => { (s.direct += s.change) && Math.random() > 0.96 && (s.change *= -1) });
        (this.lifespan > 0 && this.lifespan--) || this.remove(index, array);
    }

    this.render = function(ctx) {
        if (this.lifespan <= 0) return;
        ctx.beginPath();
        ctx.globalAlpha = this.lifespan / this.maxlife;
        ctx.strokeStyle = this.color;
        ctx.lineWidth = this.width;
        ctx.shadowBlur = 32;
        ctx.shadowColor = this.glow;
        ctx.moveTo(this.x, this.y);
        let prev = { x: this.x, y: this.y };
        this.segments.forEach(s => {
            const x = prev.x + Math.cos(s.direct) * s.length;
            const y = prev.y + Math.sin(s.direct) * s.length;
            prev = { x: x, y: y };
            ctx.lineTo(x, y);
        });
        ctx.stroke();
        ctx.closePath();
        ctx.shadowBlur = 0;
        const strength = Math.random() * 80 + 40;
        const light = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, strength);
        light.addColorStop(0, 'rgba(250, 200, 50, 0.6)');
        light.addColorStop(0.1, 'rgba(250, 200, 50, 0.2)');
        light.addColorStop(0.4, 'rgba(250, 200, 50, 0.06)');
        light.addColorStop(0.65, 'rgba(250, 200, 50, 0.01)');
        light.addColorStop(0.8, 'rgba(250, 200, 50, 0)');
        ctx.beginPath();
        ctx.fillStyle = light;
        ctx.arc(this.x, this.y, strength, 0, Math.PI * 2);
        ctx.fill();
        ctx.closePath();
    }

    this.remove = function(index, array) {
        array.splice(index, 1);
    }
}

function Spark(options) {
    options = options || {};
    this.x = options.x || w * 0.5;
    this.y = options.y || h * 0.5;
    this.v = options.v || { direct: Math.random() * Math.PI * 2, weight: Math.random() * 14 + 2, friction: 0.88 };
    this.a = options.a || { change: Math.random() * 0.4 - 0.2, min: this.v.direct - Math.PI * 0.4, max: this.v.direct + Math.PI * 0.4 };
    this.g = options.g || { direct: Math.PI * 0.5 + (Math.random() * 0.4 - 0.2), weight: Math.random() * 0.25 + 0.25 };
    this.width = options.width || Math.random() * 3;
    this.lifespan = options.lifespan || Math.round(Math.random() * 20 + 40);
    this.maxlife = this.lifespan;
    this.color = options.color || '#feca32';
    this.prev = { x: this.x, y: this.y };

    this.update = function(index, array) {
        this.prev = { x: this.x, y: this.y };
        this.x += Math.cos(this.v.direct) * this.v.weight;
        this.x += Math.cos(this.g.direct) * this.g.weight;
        this.y += Math.sin(this.v.direct) * this.v.weight;
        this.y += Math.sin(this.g.direct) * this.g.weight;
        this.v.weight > 0.2 && (this.v.weight *= this.v.friction);
        this.v.direct += this.a.change;
        (this.v.direct > this.a.max || this.v.direct < this.a.min) && (this.a.change *= -1);
        this.lifespan > 0 && this.lifespan--;
        this.lifespan <= 0 && this.remove(index, array);
    }

    this.render = function(ctx) {
        if (this.lifespan <= 0) return;
        ctx.beginPath();
        ctx.globalAlpha = this.lifespan / this.maxlife;
        ctx.strokeStyle = this.color;
        ctx.lineWidth = this.width;
        ctx.moveTo(this.x, this.y);
        ctx.lineTo(this.prev.x, this.prev.y);
        ctx.stroke();
        ctx.closePath();
    }

    this.remove = function(index, array) {
        array.splice(index, 1);
    }
}

function Particles(options) {
    options = options || {};
    this.max = options.max || Math.round(Math.random() * 10 + 10);
    this.sparks = [...new Array(this.max)].map(() => new Spark(options));

    this.update = function() {
        this.sparks.forEach((s, i) => s.update(i, this.sparks));
    }

    this.render = function(ctx) {
        this.sparks.forEach(s => s.render(ctx));
    }
}

function Text(options) {
    options = options || {};
    const pool = document.createElement('canvas');
    const buffer = pool.getContext('2d');
    pool.width = w;
    buffer.fillStyle = '#000000';
    buffer.fillRect(0, 0, pool.width, pool.height);

    this.size = options.size || 100;
    this.copy = (options.copy || `Hello!`) + ' ';
    this.color = options.color || '#cd96fe';
    this.delay = options.delay || 5;
    this.basedelay = this.delay;
    buffer.font = `${this.size}px Comic Sans MS`;
    this.bound = buffer.measureText(this.copy);
    this.bound.height = this.size * 1.5;
    this.x = options.x || w * 0.5 - this.bound.width * 0.5;
    this.y = options.y || h * 0.5 - this.size * 0.5;

    buffer.strokeStyle = this.color;
    buffer.strokeText(this.copy, 0, this.bound.height * 0.8);
    this.data = buffer.getImageData(0, 0, this.bound.width, this.bound.height);
    this.index = 0;

    this.update = function() {
        if (this.index >= this.bound.width) {
            this.index = 0;
            return;
        }
        const data = this.data.data;
        for (let i = this.index * 4; i < data.length; i += (4 * this.data.width)) {
            const bitmap = data[i] + data[i + 1] + data[i + 2] + data[i + 3];
            if (bitmap > 255 && Math.random() > 0.96) {
                const x = this.x + this.index;
                const y = this.y + (i / this.bound.width / 4);
                thunder.push(new Thunder({
                    x: x,
                    y: y
                }));
                Math.random() > 0.5 && particles.push(new Particles({
                    x: x,
                    y: y
                }));
            }
        }
        if (this.delay-- < 0) {
            this.index++;
            this.delay += this.basedelay;
        }
    }

    this.render = function(ctx) {
        ctx.putImageData(this.data, this.x, this.y, 0, 0, this.index, this.bound.height);
    }
}

function loop() {
    update();
    render();
    requestAnimationFrame(loop);
}

function update() {
    text.update();
    thunder.forEach((l, i) => l.update(i, thunder));
    particles.forEach(p => p.update());
}

function render() {
    ctx.globalCompositeOperation = 'source-over';
    ctx.globalAlpha = 1;
    ctx.fillStyle = '#000000';
    ctx.fillRect(0, 0, w, h);
    //
    ctx.globalCompositeOperation = 'screen';
    text.render(ctx);
    thunder.forEach(l => l.render(ctx));
    particles.forEach(p => p.render(ctx));
}

(function () {
    //
    canvas = document.getElementById('canvas');
    input = document.getElementById('input');
    ctx = canvas.getContext('2d');
    w = window.innerWidth;
    h = window.innerHeight;
    canvas.width = w;
    canvas.height = h;
    thunder = [];
    particles = [];
    //
    text = new Text({
        copy: 'Anh Yêu Em!'
    });
    canvas.addEventListener('click', (e) => {
        const x = e.clientX;
        const y = e.clientY;
        thunder.push(new Thunder({
            x: x,
            y: y
        }));
        particles.push(new Particles({
            x: x,
            y: y
        }));
    });
    let cb = 0;
    input.addEventListener('keyup', (e) => {
        clearTimeout(cb);
        cb = setTimeout(() => {
            text = new Text({
                copy: input.value
            });
        }, 300);
    });
    //
    loop();
})()
</script>
</body>
</html>

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

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

相关文章

每日坚果“鼻祖”,沃隆再闯IPO

成于坚果&#xff0c;困于坚果&#xff1f;“坚果大队长”沃隆再次闯关IPO。“每日坚果鼻祖”青岛沃隆食品股份有限公司&#xff08;下称“沃隆”&#xff09;于1月6日更新招股书&#xff0c;拟登陆上交所主板。沃隆是一家以坚果相关产品为核心的休闲食品生产商&#xff0c;主要…

SAP PP 生产版本主数据维护

PP生产版本主数据 生产版本&#xff08;Production Version&#xff09;主数据是执行生产业务过程中最主要的基础数据之一&#xff0c;包含了产品的数量结构信息&#xff0c;同时也包含了产品的工艺路线&#xff0c;工作中心等信息。生产版本主记录里包含了产品代码、工艺路线、…

三,Spring AOP

Spring AOP 1 代理设计模式 1.1 解决的问题 Service层中除了核心代码&#xff08;调用dao逻辑判断&#xff09;外&#xff0c;还有事务控制这种额外的增强功能。现在我们将核心业务代码和事务控制增强编码在一起直接定义在service层&#xff0c;日后还可能会新增其它的额外功…

docker安装与基本介绍使用

Docker 一、初识Docker 1、安装Docker # 1.yum包更新到最新 yum update # 2.安装需要的软件包&#xff0c;yum-util提供的yum-config-manager&#xff0c;例外两个是devicemapper驱动依赖的 yum install -y yum-utils device-mapper-persistent-data lvm2 # 3.设置yum源 yum…

hololens2开发环境配置,游戏引擎的全流程安装部署

要进行hololens 2的项目开发&#xff0c;进行基础环境搭建和软件安装部署的记录。 软件安装 UE ue2.6x(安装hololens平台)openxr插件&#xff0c;ux插件&#xff08;可选&#xff09; unity3d unity3d 2020&#xff08;unity3d 2021在打包时&#xff0c;在universal windo…

预测2023:智算中心将人工智能产业推上发展的“拐点”?

文|智能相对论作者|沈浪回顾过去的2022年&#xff0c;人工智能产业继续在巨变中迎来突破性成长。一方面&#xff0c;人工智能产业相关的应用越来越丰富、创新&#xff0c;比如元宇宙的出现和走红&#xff0c;为市场创造的一个全新的业态&#xff0c;也为用户带来了诸多新奇的体…

RabbitMQ 部署及配置详解

一、RabbitMQ 核心概念1. 生产者和消费者Producer: 消息的生产者,用于发布消息&#xff1b;Consumer: 消息的消费者&#xff0c;用于从队列中获取消息.消费者只需关注队列即可&#xff0c;不需要关注交换机和路由键。消费者可以通过basicConsume(订阅模式可以从队列中一直持续的…

【PCB专题】案例:PCB设计失误导致无法正常贴片

案例情况说明 本案例是新人画PCB的时候遇到的,但是遇到的时间已经比较晚了,在生产的时候报的异常。一部分原因也是前期我没有审查出来这个问题。 要生产的时候,工厂报了异常。新人转给我看的时候,我才发现PCB上是没有设计MARK点。这将导致SMT时没有Mark点对位,需要看看有没…

一篇搞定JS的位运算(公式+力扣真题)--- 持续更新

摘要 位操作&#xff08;Bit Manipulation&#xff09;是程序设计中对位模式或二进制数的一元和二元操作。在许多古老的微处理器上&#xff0c;位运算比加减运算略快&#xff0c;通常位运算比乘除法运算要快很多。在现代编程语言中&#xff0c;情况并非如此&#xff0c;很多编程…

Java项目:酒店管理系统(java+SSM+jsp+mysql+maven)

源码获取&#xff1a;博客首页 "资源" 里下载&#xff01; 主要技术:java springmvc mybatis mysql tomcat js jauery jsp log4j等一些常见基本技术适用于Java毕设和学习使用 主要实现&#xff1a; 前台&#xff1a;登录、注册、酒店信息浏览、搜索酒店信息、查看房…

游戏开发 帧同步

帧同步技术是一个古老的技术&#xff0c;没有特别固定的招式&#xff0c;优化方向也是根据实际情况各有不同&#xff0c;但是其核心思想都是一样的。1.为什么需要帧同步技术帧同步主要是为了保证某些类型的游戏在同步时&#xff0c;可以保证很好的游戏体验。游戏类型通常包括&a…

无线wifi的参数即含义

WiFi属性信息 参考&#xff1a;WiFi发展史&#xff1a;https://zhuanlan.zhihu.com/p/74348591&#xff0c;WiFi2.4和5G简述&#xff1a;https://zhuanlan.zhihu.com/p/344652613 下面图中的WiFi4和WiFi5是根据协议的另一个名字。&#xff08;图片来自参考链接&#xff09; …

vue3 antd项目实战——Modal弹窗自定义遮罩 (利用maskStyle属性自定义遮罩样式)

vue3 antd项目实战——Modal弹窗自定义遮罩 maskStyle知识回调场景复现关于mask遮罩的API属性利用maskStyle属性自定义遮罩样式知识回调 文章内容文章链接vue3 antd ts实战——ant-design-vue组件库引入https://blog.csdn.net/XSL_HR/article/details/127396384?spm1001.2014…

随手查——Multisim

关于电路仿真软件Multisim的一些记录&#xff0c;基于Multisim14.0。 Multisim1、Multisim中元器件的颜色2、快速创建一个放大器电路3、如何进行参数扫描&#x1f680;参数扫描数据点导出为Excel&#x1f680;退出参数扫描模式4、交流分析1、Multisim中元器件的颜色 Multisim中…

YOLOV8 | 最先进的 YOLO 模型训练自己的数据集(持续更新中)

本文实现了俩种环境的设置&#xff0c;一种是windows的CPU版本&#xff0c;还有服务器上的GPU版本。CPU版本仅用来实现检测&#xff0c;而GPU版本用来训练自己的数据集&#xff01;&#xff08;选择其中一个环境运行后&#xff0c;训练自己的数据集&#xff09;1.环境1&#xf…

C#,图像二值化(22)——局部阈值的伯恩森算法(Bernsen Thresholding)及源程序

1、局部阈值的伯恩森算法&#xff08;Bernsen Thresholding&#xff09;Bernsen方法是为图像分割开发的局部自适应二值化方法之一。在这项研究中&#xff0c;实现了Bernsen的局部自适应二值化方法&#xff0c;并对不同灰度图像进行了测试。Bernsen’s method is one of locally…

04_FreeRTOS任务挂起和恢复函数

目录 任务的挂起与恢复的API函数 任务挂起函数介绍 任务恢复函数介绍 中断中恢复函数 vTaskSuspend()任务挂起函数 vTaskResume()任务中恢复函数 xTaskResumeFromISR()中断中恢复函数 任务的挂起与恢复的API函数 挂起:挂起任务类似暂停,可恢复;删除任务,无法恢复,类似“…

公务员行测常识积累(持续更新中)

公务员行测常识积累政治天文地理人文戏曲历史经济物理生物医学政治 区域协调发展战略&#xff1a;以城市群为主体构建大中小城市和小城镇协调发展的城镇格局&#xff1b;以疏解北京非首都功能为“牛鼻子”推动京津冀协同发展&#xff1b;以共抓大保护、不搞大开发为导向推动长…

个人建议【建议】

以下只是个人的一些看法 本文已在CSDN博客中发布文章 本文已在CSDN建议社区中发布帖子 重点内容已经被蓝色字体标志出来了&#xff0c;希望能对建设优秀的CSDN有所启发 快速浏览看总结 中心思想看最后 1.我的专栏上限问题还没解决 在2022-10-24 20:33:41就发出了这个问题&…

如何突破以往模式的束缚,如何让互联网行业重新开启新的想象空间

在流量和资本的红利已然被出清的大背景下&#xff0c;以平台经济为代表的互联网经济的发展同样被逼退到了进退维谷的境地里。如何突破以往发展模式的束缚&#xff0c;如何让互联网行业的发展重新开启新的想象空间&#xff0c;成为每一个互联网玩家必然需要思考的重要课题。于是…