分享一个CSS的垂帘效果

news2024/11/25 0:53:59

先上效果图:
在这里插入图片描述
再上代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        html, body, canvas {
            -webkit-user-select: none;
            -moz-user-select: none;
            -ms-user-select: none;
            user-select: none;
        }
        html, body {
            width: 100%;
            height: 100%;
            overflow: hidden;
            margin: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            background: #191919;
        }
        .asset-img {
            display: none;
        }
    </style>
</head>
<body>
<canvas></canvas>
<img class="asset-img" id="light-img" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAMAAADzN3VRAAAABGdBTUEA
ALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACnVBMVEUAAAD40ED40D740Dv40En40Dr40FH40Df40D/40D340
Dn40Dz40Dj40AD40Ff40Db40DT40EH40EL40ED40ED40Dv40D/4zz/4zz74zz/4zz/40D74zz/4zz/4zz/40UH40kD40UL40kH4zz/4zz340D34zz740kH41U
T410f410j410j410f40D/40D/4zz/40ED40EH400T410f42Er4103410/411D41UT40kH40D/4z0D4zz/41UX410j42E7411L42Ff42Vv42Vz42En41kb40UH
4zz/40Dz40ED41UT42Ej4107411b42l743WT432r4323432r410/42En4zz340Dn400H410f42mD432v44nf45IP45Yn45IT44nj432r411b400L40D/4zz/4
1UT42Er411P432v44oD46Zv47q/48Lj46Zr432n40UH410f42Ff44nf48r/499T4997499T48r744nf43WP40D741EH42Vv4+O34+PP4+O347q745IL432j40
UD432345Yj48LX49934+PP4+Pj4993477T45Ib432z411D400D40D/40UH432n45IP47a7432j42Vv410/400H40Dr4zz340UD410b410346Zj48r343WP42F
f410f40D/41kT42Er42l744nv46Zf42l3411L410r41kX40ED410f42E7411b421/45IL45Ib432n421740UH40D340Dz4zz741EP410j410743WT432f432v
43mj43GT42Ej41kT4zz740ED41EX411P42Ff42Fv411f410j41kX400P41034107411D42E340D740UH410j41kb41ET400H40D/40ED4z0D4zz74zz340D//
//+WdN8lAAAA3nRSTlMAAAAAAAAAAAAAAAAAAAAAAAAAAgMCBAcJDAkDCA0SFRsbGxMDBQscJSwwMS0SDAUFFyExQExUVyIYDQULJzlOZHeDhDonFwoDASE5V
XGNpbO4tFM5BwEcMJa3zdjb2Mu2cBwDESM+Y7bU4ebo4bUUK3bL6vDy7+rKogIZgPb49ebXsBu02+jx9/jw6Nu0VRsBGbDX5q9/UhkCCRUqS+HqoHMqESI9i9
Thi2E9IhEvTG+V1tq0kxsDAQchN1Ggr7SvoDghChUjYXJ/cjgnIUlSVEkFGi8qIhsFFAcICAaJi/ApAAAAAWJLR0Te6W7imwAAAAd0SU1FB+YMHBEUINvSacM
AAAIKSURBVCjPY2AgBBgZmZiFRYQZgQBVnEVUTFxCUlJSSlxMlAVJjplVWkZWTl5BUUleWVZGhZkZYZKqmpy6hqaWtpaOhrqunj7MREZGA1lDI2MTUzNzM1MT
YwtLK2uIFCOjiI2hrZ29g6OTs5Ojg72Lq5u7Bxs7SMZTXNfL28fXzz8gMMjfzzc4xEI3NIwVrEUyPMLeNzIqOiY2Lj4hMtE+Ikk2mYkRKCOekpqW7peRmZWdk
52bmeeXnqYhJ84BkpHKLzAt9C/KKi4pLSvPragsNNXMl/JkZGCqUqvWMqsJiMkuqa2rL2tobKox06qWTOZg4EiWbNY2d25pbWvv6Ozo6u7pde7T6pf04GTgmG
AzEahn0uSGsvqO+rIpjVOnTdeaoTaTmYGLfdbsOXMLKyvmzS/rKps/r2jBwrmL5KWAfmViEl+8ZGn6srzlK6Z0T1mxPG/lqtVr1oLdxp0sqbRu/YaNeUWbNm8
q2rJ1w/p12yS38wDDh3PHTrldu/dsWLZ33/4DB5dt2HPosO7OMFBwM/KKHjl6bPf64yemOZ88dXz96TPyR6T5+EFByi6ganP07LrV585fmH5x6bqzs21UmZhg
0XDJXe7ykjnrtK5cvXZdzv0GMycs6jiZpXdKLs532+Z2M0VSXAQ5KTAKeorcuu1uc+TO3e07hNDSCCMjNyczKz8nN8FkxgAAgH2hXtQzzA4AAAAldEVYdGRhd
GU6Y3JlYXRlADIwMjItMTItMjhUMTc6MjA6MzIrMDA6MDA1MNVoAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTEyLTI4VDE3OjIwOjMyKzAwOjAwRG1t1AAAAA
BJRU5ErkJggg==" alt="base64">

</body>
<script>
    class Mouse {
        constructor(canvas) {
            this.pos = new Vector(-1000, -1000)
            this.radius = 40

            canvas.onmousemove = e => this.pos.setXY(e.clientX, e.clientY)
            canvas.ontouchmove = e => this.pos.setXY(e.touches[0].clientX, e.touches[0].clientY)
            canvas.ontouchcancel = () => this.pos.setXY(-1000, -1000)
            canvas.ontouchend = () => this.pos.setXY(-1000, -1000)
        }
    }
    class Dot {
        constructor(x, y) {
            this.pos = new Vector(x, y)
            this.oldPos = new Vector(x, y)
            this.friction = 0.97
            this.gravity = new Vector(0, 0.6)
            this.mass = 1
            this.pinned = false
            this.lightImg = document.querySelector('#light-img')
            this.lightSize = 15
        }

        update(mouse) {
            if (this.pinned) return
            let vel = Vector.sub(this.pos, this.oldPos)
            this.oldPos.setXY(this.pos.x, this.pos.y)
            vel.mult(this.friction)
            vel.add(this.gravity)
            let { x: dx, y: dy } = Vector.sub(mouse.pos, this.pos)
            const dist = Math.sqrt(dx * dx + dy * dy)
            const direction = new Vector(dx / dist, dy / dist)
            const force = Math.max((mouse.radius - dist) / mouse.radius, 0)
            if (force > 0.6) this.pos.setXY(mouse.pos.x, mouse.pos.y)
            else {
                this.pos.add(vel)
                this.pos.add(direction.mult(force))
            }
        }

        drawLight(ctx) {
            ctx.drawImage(
                this.lightImg,
                this.pos.x - this.lightSize / 2, this.pos.y - this.lightSize / 2, this.lightSize, this.lightSize
            )
        }

        draw(ctx) {
            ctx.fillStyle = '#aaa'
            ctx.fillRect(this.pos.x - this.mass, this.pos.y - this.mass, this.mass * 2, this.mass * 2)
        }
    }
    class Stick {
        constructor(p1, p2) {
            this.startPoint = p1
            this.endPoint = p2
            this.length = this.startPoint.pos.dist(this.endPoint.pos)
            this.tension = 0.3
        }

        update() {
            const dx = this.endPoint.pos.x - this.startPoint.pos.x
            const dy = this.endPoint.pos.y - this.startPoint.pos.y
            const dist = Math.sqrt(dx * dx + dy * dy)
            const diff = (dist - this.length) / dist
            const offsetX = diff * dx * this.tension
            const offsetY = diff * dy * this.tension
            const m = this.startPoint.mass + this.endPoint.mass
            const m1 = this.endPoint.mass / m
            const m2 = this.startPoint.mass / m
            if (!this.startPoint.pinned) {
                this.startPoint.pos.x += offsetX * m1
                this.startPoint.pos.y += offsetY * m1
            }
            if (!this.endPoint.pinned) {
                this.endPoint.pos.x -= offsetX * m2
                this.endPoint.pos.y -= offsetY * m2
            }
        }

        draw(ctx) {
            ctx.beginPath()
            ctx.strokeStyle = '#999'
            ctx.moveTo(this.startPoint.pos.x, this.startPoint.pos.y)
            ctx.lineTo(this.endPoint.pos.x, this.endPoint.pos.y)
            ctx.stroke()
            ctx.closePath()
        }
    }
    class Rope {
        constructor(config) {
            this.x = config.x
            this.y = config.y
            this.segments = config.segments || 10
            this.gap = config.gap || 15
            this.color = config.color || 'gray'
            this.dots = []
            this.sticks = []
            this.iterations = 10
            this.create()
        }
        pin(index) {
            this.dots[index].pinned = true
        }
        create() {
            for (let i = 0; i < this.segments; i++) {
                this.dots.push(new Dot(this.x, this.y + i * this.gap))
            }
            for (let i = 0; i < this.segments - 1; i++) {
                this.sticks.push(new Stick(this.dots[i], this.dots[i + 1]))
            }
        }
        update(mouse) {
            this.dots.forEach(dot => {
                dot.update(mouse)
            })
            for (let i = 0; i < this.iterations; i++) {
                this.sticks.forEach(stick => {
                    stick.update()
                })
            }
        }
        draw(ctx) {
            this.dots.forEach(dot => {
                dot.draw(ctx)
            })
            this.sticks.forEach(stick => {
                stick.draw(ctx)
            })
            this.dots[this.dots.length - 1].drawLight(ctx)
        }
    }
    class App {
        static width = innerWidth
        static height = innerHeight
        static dpr = devicePixelRatio > 1 ? 2 : 1
        static interval = 1000 / 60
        constructor() {
            this.canvas = document.querySelector('canvas')
            this.ctx = this.canvas.getContext('2d')

            this.mouse = new Mouse(this.canvas)

            this.resize()
            window.addEventListener('resize', this.resize.bind(this))

            this.createRopes()
        }
        createRopes() {
            this.ropes = []

            const TOTAL = App.width * 0.06
            for (let i = 0; i < TOTAL + 1; i++) {
                const x = randomNumBetween(App.width * 0.3, App.width * 0.7)
                const y = 0
                const gap = randomNumBetween(App.height * 0.05, App.height * 0.08)
                const segments = 10
                const rope = new Rope({ x, y, gap, segments })
                rope.pin(0)

                this.ropes.push(rope)
            }
        }
        resize() {
            App.width = innerWidth
            App.height = innerHeight

            this.canvas.style.width = '100%'
            this.canvas.style.height = '100%'
            this.canvas.width = App.width * App.dpr
            this.canvas.height = App.height * App.dpr
            this.ctx.scale(App.dpr, App.dpr)
            this.createRopes()
        }
        render() {
            let now, delta
            let then = Date.now()
            const frame = () => {
                requestAnimationFrame(frame)
                now = Date.now()
                delta = now - then
                if (delta < App.interval) return
                then = now - (delta % App.interval)
                this.ctx.clearRect(0, 0, App.width, App.height)

                // draw here
                this.ropes.forEach(rope => {
                    rope.update(this.mouse)
                    rope.draw(this.ctx)
                })
            }
            requestAnimationFrame(frame)
        }
    }
    function randomNumBetween(min, max) {
        return Math.random() * (max - min) + min
    }
    window.addEventListener('load', () => {
        const app = new App()
        app.render()
    })
    class Vector {
        constructor(x, y) {
            this.x = x || 0
            this.y = y || 0
        }
        static add(v1, v2) {
            return new Vector(v1.x + v2.x, v1.y + v2.y)
        }
        static sub(v1, v2) {
            return new Vector(v1.x - v2.x, v1.y - v2.y)
        }
        add(x, y) {
            if (arguments.length === 1) {
                this.x += x.x
                this.y += x.y
            } else if (arguments.length === 2) {
                this.x += x
                this.y += y
            }
            return this
        }
        sub(x, y) {
            if (arguments.length === 1) {
                this.x -= x.x
                this.y -= x.y
            } else if (arguments.length === 2) {
                this.x -= x
                this.y -= y
            }
            return this
        }
        mult(v) {
            if (typeof v === 'number') {
                this.x *= v
                this.y *= v
            } else {
                this.x *= v.x
                this.y *= v.y
            }
            return this
        }
        setXY(x, y) {
            this.x = x
            this.y = y
            return this
        }
        dist(v) {
            const dx = this.x - v.x
            const dy = this.y - v.y
            return Math.sqrt(dx * dx + dy * dy)
        }
    }
</script>
</html>

代码直接粘贴到html页面就能使用,顺滑的不可言说

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

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

相关文章

[JAVA]前后端分离智慧校园电子班牌系统源码微信带小程序

1. 开发语言&#xff1a;JAVA 2. 数据库&#xff1a;MySQL 3. 后端框架&#xff1a;Spring boot 4. 前端框架&#xff1a;VUE2 5. 电子班牌&#xff1a; Android 7.1 6. 小程序&#xff1a;原生语言开发 7. 多学校Saas 模式 在大数据平台下&#xff0c;对应用系统进行统…

研报精选230428

目录 【行业230428华安证券】环保行业框架分析 【个股230428华西证券_视源股份】收入表现优于行业&#xff0c;平板盈利提升 【个股230428中银证券_长海股份】23Q1业绩承压&#xff0c;行业库存、出口现好转信号 【个股230428国信证券_星宇股份】一季度归母净利润环比增长9%&am…

Web3 游戏全景图:各类玩家群像解读

作者&#xff1a;lesleyfootprint.network 主要观点 即使市场深陷寒冬&#xff0c;但 web3 游戏每天依然有八十万的用户。 Web3 游戏正在逐步完善&#xff0c;从 DeFi 演变出的 Web3 游戏 &#xff0c;到增强了叙事的 X2E&#xff0c;再到 AAA 游戏和元宇宙。 未来&#xff…

同为科技(TOWE)科普与雷电相关的基本知识

前 言 雷电是伴有闪电和雷鸣的放电现象&#xff0c;壮观而又有点令人生畏。雷电一般产生于对流发展旺盛的积雨云中&#xff0c;常伴有强烈的阵风和暴雨&#xff0c;有时还伴有冰雹和龙卷风。夏季雷电时有发生&#xff0c;虽然常见&#xff0c;但你真的了解雷电吗&#xff1f;这…

停车场管理系统的设计与实现_kaic

目 录 1 概 述 1.1研究背景 1.2研究现状 1.3研究内容 2 相关技术简介 2.1 JSP技术 2.2 JAVA技术 2.3 MYSQL数据库 2.4 B/S结构 3 系统需求分析 3.1 系统可行性分析 3.1.1 操作可行性 3.1.2 经济可行性 3.1.3 技术可行性 3.2 系统性能分析 3.3系统流程分析 3.3.1注册流程 3.3.…

关于Prompt Engineering你该了解啥?OpenAI应用研究负责人帮你梳理了

‍来源 | 机器之心 微信号&#xff1a;almosthuman2014 随着 ChatGPT、GPT-4 等模型的兴起&#xff0c;人们对如何创建提示以获得想要的输出越来越感兴趣。研究者对特定提示的响应可能很难预测&#xff0c;并且会因模型的不同而不同。本文来自 OpenAI 的翁丽莲&#xff08;Lili…

【C语言】深度剖析数据在内存中的存储

简单不先于复杂&#xff0c;而是在复杂之后。 目录 1. 数据类型介绍 1.1 类型的基本归类 2.整型在内存中的存储 2.1 原码、反码、补码 2.2 大小端介绍 2.3 练习 2.3.1 练习1 2.3.2 练习2 3.2.3 练习3 2.3.4 练习4 2.3.5 练习5 2.3.6 练习6 2.3.7 练习7 …

linux jstat 简介

本文目录一览&#xff1a; 1、Linux使用jstat命令查看jvm的GC情况2、linux怎么监控 jvm内存 jstat3、Linux系统监控要用到哪些命令4、linux上如何安装jstatd服务 Linux使用jstat命令查看jvm的GC情况 Linux 使用jstat命令查看jvm的GC情况 命令格式 jstat命令命令格式&#…

【Linux】3. 基本权限与文件指令

1. 用户概念 sudo提权配置 2. 权限的定义 3. 文件权限 角色和文件属性是一一对应的关系&#xff1a; 拥有者-所属组-其他人 rwx - rwx - rwx 4. 文件类型 5. chmod 修改文件属性 6. chown/chgrp 修改文件所属角色 7. umask 8. file指令 9. 目录的权限 10. 粘滞位 首先要明确…

Arduino ESP8266 基于本地搭建MQTT服务运行

Arduino ESP8266 基于本地搭建MQTT服务运行 📌相关篇《Arduno ESP8266接入OneNET实时显示DHT11数据》 🔨本地架设MQTT服务端软件:EMQX:https://www.emqx.io/zh/downloads?os=Windows - 🔧MQTT本地客户端软件:mqttx:https://mqttx.app/ 📺服务端接收设备段上传的数据…

人群计数:技术难点、商业产品化成功案例、现状、传统做法、硬件设备、

现状&#xff1a; 比较成熟了&#xff0c;准确率已经很高了&#xff08;有多高&#xff0c;后面我搞懂metrics高到什么程度&#xff0c;再汇报过来&#xff09; 商业公司基本把这个领域做的很透彻了&#xff0c;performance基本到了一一个无法提高的位置了&#xff08;和图像…

卡尔曼滤波器简介——多维卡尔曼滤波

原文&#xff1a;多维卡尔曼滤波 (kalmanfilter.net) 目录 前言 基本背景 状态外推方程 示例 - 飞机 - 无控制输入 示例 - 带控制输入的飞机 示例 – 坠落物体 状态外推方程维度 线性时不变系统 线性动态系统建模 状态外推方程的推导 状态空间表示形式 示例 - 等速…

大数据Doris(六):BE部署及启动

文章目录 BE部署及启动 一、上传安装包并解压 二、修改be.conf 配置文件 三、上传apache-doris-java-udf 对应 jar 四、启动BE 五、将 node3 BE 安装包发送其他 BE 节点 六、配置其他 BE 节点 七、启动其他 BE 节点 BE部署及启动 本集群中我们在node3、node4、node5上…

C S S

目录 1.样式定义方式 1.1行内样式表 1.2内部样式表 1.3外部样式表 2.注解 3.选择器 3.1标签选择器 3.2 id选择器 3.3 类选择器 3.4 派生选择器 3.5 伪类选择器 链接伪类选择器&#xff1a; 位置伪类选择器&#xff1a; ​编辑 目标伪类选择器&#xff1a; 复合选…

数据治理在学术上的发展史以及未来展望

数据治理是大数据领域中非常重要的一环&#xff0c;从早期的学术研究到如今的各大企业落地实践&#xff0c;经历了漫长的过程&#xff0c;数据治理的实践落地本身也是一场马拉松。 从百度学术通过精确关键词匹配&#xff0c;搜索中文期刊的“数据治理” 和外文期刊的“data gov…

Apache安装与基本配置

1. 下载apache 地址&#xff1a;www.apache.org/download.cgi&#xff0c;选择“files for microsoft windows”→点击”ApacheHaus”→点击”Apache2.4 VC17”&#xff0c;选择x64/x86&#xff0c;点击右边download下面的图标。 2. 安装apache &#xff08;1&#xff09;把…

flutter集成Mob推送(Android)

Mob推送 Flutter对接文档 1、在pubspec.yaml文件中加入下面依赖 mobpush_plugin: ^1.2.2 # MOB推送2、导入 MobPush 相关依赖 在项目根目录的build.gradle中添加以下代码&#xff1a; buildscript {repositories {// 配置Mob Maven库maven {url "https://mvn.mob.com/an…

4月份读书学习好文记录

4月份学习记录 找到自己感兴趣的方向&#xff0c;而不是人云亦云&#xff0c;知道自己想要的是什么&#xff0c;而不是一直得过且过&#xff01; 差距是怎么出现的&#xff0c;四年来的点点滴滴&#xff01;&#xff01;&#xff01; 一个前端大佬的十年回顾 | 漫画前端的前世…

Confidential Containers发布0.5.0版本,龙蜥将基于八大特性构建开箱即用的机密容器解决方案

文/段勇帅 01 前言 机密容器&#xff08;Confidential Containers&#xff0c;简称CoCo&#xff09;是 Cloud Native Computing Foundation&#xff08;CNCF&#xff09;Sandbox 项目。目前机密容器项目的核心参与者包括阿里云、AMD、ARM、IBM、Intel、Microsoft、Red Hat、R…

OpenGL(三)——着色器

目录 一、前言 二、Shader 2 Shader 2.1 顶点着色器 2.2 片段着色器 三、APP 2 Shader 四、顶点颜色属性 五、着色器类C 一、前言 着色器Shader是运行在GPU上的小程序&#xff0c;为图形渲染管线的某个特定部分而运行。各阶段着色器之间无法通信&#xff0c;只有输入和输…