HTML实现激光文字

激光文字


<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>激光文字</title>
    <style>
        .page-laser-to-text {
            position: relative;
            overflow: hidden
        }

        .page-laser-to-text canvas {
            display: block
        }

        .page-laser-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-laser-to-text input:hover,
        .page-laser-to-text input:focus {
            border: 1px solid rgba(38, 50, 56, 0.6)
        }

        .page-laser-to-text input::-webkit-input-placeholder {
            color: rgba(255, 255, 255, 0.1)
        }
    </style>
</head>

<body>
    <div class="page page-laser-to-text">
        <input id="input" type="text" maxlength="24" placeholder="I love you!">
        <canvas id="canvas"></canvas>
    </div>
</body>
<script>
    let canvas, ctx, w, h, laser, text, particles, input;
    function Laser(options) {
        options = options || {};
        this.lifespan = options.lifespan || Math.round(Math.random() * 20 + 20);
        this.maxlife = this.lifespan; this.color = options.color || '#fd2423';
        this.x = options.x || Math.random() * w;
        this.y = options.y || Math.random() * h;
        this.width = options.width || 2;
        this.update = function (index, array) { 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(w, this.y);
            ctx.stroke();
            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() * 10 + 2, friction: 0.94
        };
        this.a = options.a || {
            change: Math.random() * 0.2 - 0.1, 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.5 + 0.5
        };
        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 || '#fdab23';
        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 *= 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() * 20 + 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 || '#fecd96';
        this.delay = options.delay || 0.3;
        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.86) {
                    const x = this.x + this.index;
                    const y = this.y + (i / this.bound.width / 4);
                    laser.push(new Laser({ x: x, y: y }));
                    Math.random() > 0.7 && particles.push(new Particles({ x: x, y: y }))
                }
            }
            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(); laser.forEach((l, i) => l.update(i, laser)); 
        particles.forEach(p => p.update())
    }
    function render() {
        ctx.globalCompositeOperation = 'source-over';
        ctx.globalAlpha = 0.9; ctx.fillStyle = '#000000';
        ctx.fillRect(0, 0, w, h);
        ctx.globalCompositeOperation = 'screen';
        text.render(ctx); laser.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;
        laser = [];
        particles = [];
        text = new Text({ copy: 'JiuTian' });
        canvas.addEventListener('click', (e) => {
            const x = e.clientX; const y = e.clientY; laser.push(new Laser({ 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>

</html>

预览   |   下载

激光文字   下载

感谢各位的阅读,更多文章请前往九天小窝

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值