找到
54
篇与
Web前端
相关的结果
- 第 3 页
-
getHTML() - 替代 innerHTML 的最佳方法 随着所有主流浏览器现已支持 getHTML() 方法,前端开发者有了一个强大的新工具来操作DOM。本文主要探讨 getHTML()的独特优势,特别是在处理Shadow DOM时的卓越表现。 getHTML()与innerHTML的异同 getHTML()和 innerHTML 的 getter 在基本功能上相似,都返回元素内部DOM树的HTML表示。但getHTML()的真正优势在于它能够包含Shadow DOM的HTML,而innerHTML则完全忽略Shadow DOM。 getHTML()的高级用法 getHTML()接受一个可选的options对象参数,通过适当的选项可以获取完整的HTML,包括Shadow DOM: const container = document.body; const host = createDiv(123); const root = attachShadowDOM(host); container.append(host); console.log(container.getHTML({ shadowRoots: [root] }));图片 这段代码会返回包含声明式Shadow Root的完整HTML: <div> <template shadowrootmode="open"> <p>Paragraph <slot>default</slot></p> </template> 123 </div>如果在浏览器中将返回的 上面的 HTML 作为新页面打开,则会再现原始 DOM 树: 图片 通常,shadow trees和slots是在自定义元素的构造函数中创建的,但为了保持上面和下面示例页面中的代码简单,这里没有创建任何自定义元素。相反,使用了两个辅助函数: // shared.js export function attach(host) { const shadowRoot = host.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = '<p>Paragraph <slot>default</slot></p>'; return shadowRoot; } export function div(n) { const el = document.createElement('div'); if (n) el.innerHTML = n; return el; }div(n)创建一个新的div元素,里面包含数字n,例如<div>123</div>,而attach(host)将HTML为<p>Paragraph <slot>default</slot></p>的shadow树附加到host元素上。为了用常见情况挑战getHTML(),div中的数字123被分配到shadow DOM的slot中。 处理嵌套的Shadow DOM 在上面的页面中,getHTML()被调用时使用了所有两个可能的选项: <script type="module"> import { attach, div } from './shared.js'; const container=document.body; const host=div(123); const root=attach(host); container.append(host); console.log('>innerHTML',container.innerHTML); console.log('>getHTML',container.getHTML()); console.log('>getHTML2',container.getHTML({ serializableShadowRoots: true })); console.log('>getHTML3',container.getHTML({ shadowRoots: [root] })); </script>options对象可以有两个属性:serializableShadowRoots和shadowRoots。 当getHTML()在没有options的情况下被调用时,Shadow DOM会被忽略,就像在innerHTML中一样。 如果serializableShadowRoots为true,HTML将包括具有serializable属性设置为true的shadow roots。这样的roots通常不应该存在,因为serializable是与getHTML()一起引入的,默认情况下它是false。 要获取shadow roots的HTML,需要在shadowRoots属性中提供要序列化的shadow roots。当shadow roots是open的时候,可以很容易地递归检索网页中的所有shadow roots。在网页上下文中无法检索closed shadow roots,但可以在浏览器扩展注入的内容脚本中检索。 提供的shadow roots不一定会被序列化。在下一个示例页面中,创建了两个shadow trees。第二个shadow DOM嵌套在第一个中: <script type="module"> import { attach, div } from './shared.js'; const container=document.body; const host=div(123); const root=attach(host); container.append(host); const host2=div(456); const root2=attach(host2); container.append(host); root.append(host2); console.log('>innerHTML',container.innerHTML); console.log('>getHTML',container.getHTML()); console.log('>getHTML2',container.getHTML({ serializableShadowRoots: true })); console.log('>getHTML3',container.getHTML({ shadowRoots: [root] })); console.log('>getHTML4',container.getHTML({ shadowRoots: [root2] })); console.log('>getHTML5',container.getHTML({ shadowRoots: [root,root2] })); </script>如果第一个shadow DOM不包含在options中,getHTML()不会返回第二个shadow DOM的HTML: 要被序列化,shadow roots需要直接连接到要被序列化的DOM。如果省略了父shadow root,嵌套的shadow root也不会被序列化。 getHTML 局限性 缺少outerHTML等价物:目前还没有获取包含元素自身在内的HTML的方法。 单根元素限制:getHTML()返回的HTML如果没有单一根元素,浏览器可能无法正确解析为声明式Shadow DOM。 封闭的Shadow DOM:在网页上下文中无法获取封闭的Shadow DOM,但可以通过浏览器扩展的内容脚本来实现。 结语 getHTML()为开发者提供了一种强大的方法来处理包含Shadow DOM的复杂DOM结构。虽然它有一些限制,但在处理现代Web组件和复杂UI时,getHTML()的优势是显而易见的。随着Web组件的普及,掌握getHTML()将成为前端开发者的重要技能。 在实际开发中,getHTML()可以用于创建更精确的DOM快照、调试复杂的组件结构,以及在需要保留Shadow DOM结构的情况下序列化页面内容。随着Web标准的不断发展,我们可以期待看到更多类似getHTML()这样的强大API,进一步增强前端开发的能力和灵活性。
-
超高级的CSS印章效果!附源码!! 效果预览 图片 源码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>印章</title> </head> <body> <canvas id="canvas" width="250" height="250"></canvas> </body> <script> function createSeal(id, company, name) { var canvas = document.getElementById(id); var context = canvas.getContext('2d'); // 绘制印章边框 var width = canvas.width / 2; var height = canvas.height / 2; context.lineWidth = 7; context.strokeStyle = "#f00"; context.beginPath(); context.ellipse(width, height, 110, 80, 0, 0, Math.PI * 2); // 修改为椭圆形 context.stroke(); // 绘制印章中间的文字 context.font = '16px Helvetica'; context.textBaseline = 'middle';//设置文本的垂直对齐方式 context.textAlign = 'center'; //设置文本的水平对对齐方式 context.lineWidth = 1; context.fillStyle = '#f00'; context.fillText('全国二次元聚集地', width, height + 5); // 绘制印章名称 context.font = '20px Helvetica'; context.textBaseline = 'middle';//设置文本的垂直对齐方式 context.textAlign = 'center'; //设置文本的水平对对齐方式 context.lineWidth = 1; context.fillStyle = '#f00'; context.fillText(name, width, height + 55); // 绘制印章单位 context.translate(width, height); // 平移到此位置 context.font = '18px Helvetica'; var count = company.length; // 字数 var angle = 2 * Math.PI / 15; // 修改为围绕椭圆的角度 var radiusX = 86; // 椭圆的X半径 var radiusY = 60; // 椭圆的Y半径 var chars = company.split(""); var c; var startAngle = Math.PI / 1; // 设置开始文字的角度 for (var i = 0; i < count; i++) { c = chars[i]; // 需要绘制的字符 var currentAngle = startAngle + angle * i; // 当前字符的角度 var x = radiusX * Math.cos(currentAngle); // X坐标 var y = radiusY * Math.sin(currentAngle); // Y坐标 context.save(); context.translate(x, y); // 平移到字符位置 context.rotate(currentAngle + Math.PI / 2); // 旋转字符 context.fillText(c, 0, 0); // 绘制字符 context.restore(); } } createSeal('canvas', '全国统一发票监制章', '749局'); </script> </html>
-
用 iframe 必定遇到过这六种“坑”之一(以 vue 为示例) 前言 如果你是做web前端,那么不可避免早晚都会用到iframe的。其实博主很久前用过,但最近又要有项目用了,由于年代久远对iframe的注意事项都有点忘记了,然后想着总结一下比较需要注意的几个重点事项,除了便于高效工作还能分享给有需要用到iframe的小伙伴。 iframe基于父窗口大小自适应宽高 「简述:」 这是iframe最常见的需求了,有时候我们用iframe嵌入一个页面时,不想固定宽高想跟随父系统屏幕大小动态变化,从而大大提高适配性。 「实现思路:」 iframe标签绑定:style 来动态设置宽高,监听父窗口宽高变化时动态获取并且绑定到:style,但监听变化需要考虑到初始化和窗口缩放的情况,并且记得移除事件监听器防止内存泄漏。 「完整实现代码如下所示」 <template> <div class="box" @resize="iframeResize"> <iframe :src="iframeSrc" :style="{ width: '100%', height: frameHeight + 'px' }" ref="myRef"></iframe> </div> </template> <script setup> import { onMounted, onUnmounted, ref, watchEffect } from 'vue'; const myRef = ref(null); const iframeSrc = 'https:******.com'; const frameHeight = ref(0); // 调整iframe的高度的方法 function initHeight() { if (myRef.value) { frameHeight.value = window.innerHeight; } } // 窗口大小变化触发 function iframeResize() { initHeight(); } // 移除事件监听器, 防止内存泄漏 onUnmounted(() => { window.removeEventListener('resize', iframeResize); }); // 在组件挂载时先获取一次iframe高度 onMounted(() => { initHeight(); window.addEventListener('resize', iframeResize); }); // 时刻监听变化,防止iframeRef没有赋值 watchEffect(() => { if (myRef.value) { initHeight(); } }); </script> <style scoped> .box { position: relative; width: 100%; height: 100vh; } </style>iframe基于内容动态宽高 「简述:」 例如我们业务需求嵌入的是一个表格而不是一个页面,并且表格高度并不确定时我们不能固定iframe的高度,否则只有一条内容或者没有内容的时候会不好看,这里要根据内容的数量去决定ifram嵌入窗口的高度。 「实现思路:」 思路是子窗口通信告诉父窗口具体高度,然后父窗口再动态设置高度即可。具体实现是子窗口利用 window.postMessage 来发送具体高度,然后父窗口用window.addEventListener('message', 方法)接收内容,从而根据接收到的内容动态调整iframe的高度。 「子窗口(被嵌入页面)代码」 <script setup> window.onload = function () { let height = '想要告诉父窗口的高度' if (window.parent && window.parent.postMessage) { window.parent.postMessage({ height: height }, '*'); } }; </script>「父窗口代码」 <template> <div> <iframe :src="iframeSrc" ref="myRef"></iframe> </div> </template> <script setup> import { onMounted, ref } from 'vue'; const iframeSrc = 'https:******.com'; const myRef = ref(null); onMounted(() => { window.addEventListener('message', iframeMessage); }); const iframeMessage = (event) => { // 验证消息来源,确保安全性 if (event.origin !== 'https:******.com') return; const newHeight = event.data.height; if (newHeight && myRef.value) { myRef.value.style.height = `${newHeight}px`; } }; </script>「注意:」 if (event.origin !== 'https:******.com') return 这行代码记得加上。 iframe嵌入页面免登录处理 这个小弟有单独写过相关详细文章,并且分析了几种情况的处理方式,移步:iframe嵌入页面实现免登录 http无法嵌入https 「简述:」 例如开发环境是HTTP,嵌入的环境是生产环境的HTTPS,这时就会发现这个问题了。其实是现代浏览器的安全机制,会认为是跨域不同源而禁止。因为HTTP协议传输的数据未加密的会有安全风险。 「解决思路:」 网上五花八门的方法,但其实真正靠谱的就两种,(1)将 HTTP 转换成 HTTPS (2)使用代理服务。 方法一:将 HTTP 转换成 HTTPS(推荐) 1、获取 SSL 证书并安装 方法不止一种,这里就细说啦。 2、配置服务器(这里以nginx为例) 下面我把核心部分解释放在注释说明,这是简单版本用于说明HTTP转HTTPS,如果真的上生产其实还有不少其它配置。 server { listen 80; // 监听http默认的80端口 return 301 https://$host$request_uri; // 把所有http永久重定向到https server_name ****.com www.****.com; // 指定域名,这里视真实情况而定 } server { listen 443 ssl; // 监听https默认的443端口。 server_name ****.com www.****.com; // 指定域名,这里视真实情况而定 // 指定 SSL 证书文件路径。 ssl_certificate /etc/lets/live/****.com/fullchain.pem; // 指定 SSL 私钥文件路径 ssl_certificate_key /etc/lets/live/****.com/privkey.pem; location / { index index.html index.htm; root /var/w/html; } }方法二:使用代理服务 用 nodejs 搭建个简单的代理服务器(这里是用 nodejs 举例,真实业务场景可能是后端那边搞) 要先安装对应的依赖,例如 npm init -y npm install express http-proxy然后再配置对应的代理服务器,主要核心是下面四个模块 express:作用是构建 Web 应用。 http:作用是处理 HTTP 请求。 https:作用是处理 HTTPS 请求。 httpProxy:作用是创建代理服务器。 const express = require('express'); const fs = require('fs'); const https = require('https'); const http = require('http'); const httpProxy = require('http-proxy'); const app = express(); const proxy = httpProxy.createProxyServer(); const port = 3000; // 读取对应的SSL证书文件 const options = { key: fs.readFileSync('/etc/lets/live/proxy.****.com/privkey.pem'), cert: fs.readFileSync('/etc/lets/live/proxy.****.com/fullchain.pem') }; // 设置路由信息 app.all('/proxy/*', (req, res) => { const targetUrl = `http://${req.params[0]}`; proxy.web(req, res, { target: targetUrl }, (error) => { res.status(500).send('Proxy request failed'); }); }); // 创建HTTPS服务器 const server = https.createServer(options, app); // 启动代理服务器 server.listen(port, () => { console.log(`HTTPS`); });跨域问题 「简述:」 iframe 页面的跨域问题是因为涉及到浏览器的安全策略,即同源策略。同源策略限制了一个网页脚本不能读写不同源页面的 DOM 与 Cookie之类的信息。即如果 iframe 中的内容与包含它的页面不在同一个源上,那么这两个页面之间会受到跨域限制。 「解决思路:」 1、使用 window.postMessage 实现跨域通信 父页面代码: 主要用window.addEventListener监听消息用postMessage发送消息。 「注意」: @load加载完成后再监听和window.removeEventListener取消监听这两个细节。 <template> <div> <iframe :src="iframeSrc" ref="iframeRef" @load="onIframeLoad" style="width: 100%; height: 400px;"></iframe> <button @click="sendMessage">发送消息</button> </div> </template> <script setup> import { ref, onMounted } from 'vue'; onMounted(() => { window.addEventListener('message', handleMessage); // 在组件卸载时移除事件监听器 return () => { window.removeEventListener('message', handleMessage); }; }); const iframeSrc = 'http://***.com'; const iframeRef = ref(null); // 当 iframe 加载完成后,再设置监听器 const onIframeLoad = () => { window.addEventListener('message', handleMessage); }; const sendMessage = () => { const iframe = iframeRef.value; if (iframe.contentWindow) { iframe.contentWindow.postMessage('Hello!', 'http://***.com'); } }; const handleMessage = (event) => { // 确保来自想要的源才处理消息 if (event.origin !== 'http://***.com') return; console.log(event.data); }; </script>子页面代码:和父页面一样,用window.addEventListener监听消息用postMessage发送消息。 <template> <div> <button @click="sendMessage">发送消息到父页面</button> </div> </template> <script setup> import { ref, onMounted } from 'vue'; onMounted(() => { window.addEventListener('message', handleMessage); // 在组件卸载时移除事件监听器 return () => { window.removeEventListener('message', handleMessage); }; }); const sendMessage = () => { const parentWindow = window.parent; if (parentWindow) { parentWindow.postMessage('Hello!', 'http://****.com'); } }; const handleMessage = (event) => { if (event.origin !== 'http://****.com') return; console.log(event.data); }; </script>2、使用 document.domain document.domain用于解决二级域名之间跨域问题的方法,例如:a.tty.com 和 b.tty.com,它们都属于同一个顶级域名 tty.com,这时就适合用document.domain来让这两个页面能够相互访问。用法相当于简单,就是分别设置两个页面的document.domain。 核心代码在第10与19行。 <template> <div> <iframe :src="iframeSrc" @load="onIframeLoad" ref="iframeRef"></iframe> </div> </template> <script setup> import { ref } from 'vue'; document.domain = 'tty.com'; // 设置顶级域名 const iframeRef = ref(null); const iframeSrc = 'http://b.tty.com'; const onIframeLoad = () => { const iframe = iframeRef.value; if (iframe.contentWindow) { // 设置iframe的 document.domain iframe.contentWindow.document.domain = 'tty.com'; } }; </script>3、使用 CORS 这里主要是后端的配置了,通过调整服务器响应头中的 Access-Control-Allow-Origin 来控制哪些源是可以安全访问资源。 以为nginx为例,*设置为所有。 http { server { listen 80; server_name yourdomain.com; # 替换为你的域名 # 代理 iframe 请求并添加 CORS 头部 location /iframe-proxy/ { # 添加CORS头部 add_header Access-Control-Allow-Origin *; # 其他配置... } } }4、nginx配置代理 算是常见解决方案了,思路是通过 Nginx 反向代理,将请求重定向到想要请求的目标服务器。 核心就是第10行代码,具体可以特意去看看nginx。 http { server { listen 80; server_name yourdomain.com; # 替换为你的域名 # 代理 iframe 请求并添加 CORS 头部 location /iframe-proxy/ { # 将请求代理到目标 proxy_pass http://tty.com/; # 其他配置... } } }iframe嵌入后报拒绝连接请求 不知道你用iframe有没有见过这个页面,这通常是目标页面设置了 X-Frame-Options 响应头来限制内容被嵌入到其他站点的 iframe 中。这个可以找后端看看 X-Frame-Options 。 图片 小结 都是把遇到的场景总结了一下,感觉都是比较常见的情况。 如果大佬们有什么 iframe 的 “坑” 也可以分享一下我同步学习一下,还有那里写的不好也可以指出更正鸭
-
手把手教你在 Vue 项目中优雅地封装 axios! 1.axios 简介 axios 是一个用来发起网络请求的 js 库,返回的格式是 Promise。 vue 项目中基本都是用 axios 发起网络请求。 2.安装配置 axios 2.1 安装 axios npm i axios -- save2.2 配置 axios 创建 Axios 实例 添加请求拦截器 添加响应拦截器 配置全局的 loading 在 src/util 下面新建 axios.js 文件 import axios from "axios"; import { ElMessage } from 'element-plus' import { ElLoading } from 'element-plus' import { ref } from 'vue' // -------------------------1. 创建axios实例----------------------- const instance = axios.create({ // 接口 baseURL: "/api", // 超时时间 timeout: 3000, }); // -------------------------2.请求拦截----------------------- instance.interceptors.request.use( config => { let token = sessionStorage.getItem('token'); if (token) { config.headers['token'] = token } // 加载loading addLoading(); return config; }, error => { // 请求发生错误,抛出异常 Promise.reject(error); } ); // -------------------------3.响应拦截----------------------- instance.interceptors.response.use( res => { // 取消加载 loading cancelLoading(); return res; }, error => { // 取消加载 loading cancelLoading(); if (error && error.response) { const status = error.response.status switch (status) { case 400: ElMessage.error("请求错误"); break; case 401: ElMessage.error("未授权,请重新登录"); break; case 403: ElMessage.error("登录过期,请重新登录"); break; case 404: ElMessage.error("请求错误,未找到相应的资源"); break; case 408: ElMessage.error("请求超时"); break; case 500: ElMessage.error("服务器错误"); break; case 504: ElMessage.error("网络超时"); break; default: ElMessage.error("请求失败"); } } else { if (JSON.stringify(error).includes("timeout")) { error.code = "TIMEOUT"; error.message = "服务器响应超时,请刷新页面"; } } return Promise.reject(error); }, ); // -------------------------4.配置全局loading----------------------- let loadCount = 0; let loadingInstance = ref(null); // 加载loading const addLoading = () => { loadCount++; if (loadCount === 1) { loadingInstance.value = ElLoading.service({ fullscreen: false, text: "正在请求数据中....", background: "rgba(0, 0, 0, 0)", }); } }; // 取消加载loading const cancelLoading = () => { loadCount--; if (loadCount === 0) { loadingInstance.value.close(); }; }; // -------------------------配置全局loading----------------------- // 5.导出 axios 实例 export default instance;2.3 封装常用的 http 请求 在 /src/util 下面新建 http.js 文件 其实就是先引入 axios 实例,然后将 axios 的几种常用网络请求封装成 Promise 并返回。import instance from "./axios"; const post = (url, data) => { return new Promise((resolve, reject) => { instance .post(url, data) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }); }; const get = (url, data) => { return new Promise((resolve, reject) => { instance .get(url, { params: data }) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }); }; const put = (url, data) => { return new Promise((resolve, reject) => { instance .put(url, data) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }); }; const del = (url, data) => { return new Promise((resolve, reject) => { instance .delete(url, { params: data }) .then((res) => { resolve(res); }) .catch((err) => { reject(err); }); }); }; export default { post, get, put, del, };2.4 开发接口 在 /src/api 文件夹下新建接口文件 import http from "../utils/http"; // 用户登录 const login = (data) => { return http.post("/index/user/login", data); }; export default { login }图片 3.请求案例 // 导入用户api import userApi from "../api/user"; // 登录 const onSubmit = async () => { const res = await userApi.login(form); if (res.data.code == 200) { // 登录逻辑 } else { ElMessage.error(res.data.message); } };图片
-
Web前端与众不同的夜间开关交互效果(附源码) 图片 这是一个精美炫酷的日/夜开关切换按钮效果。这个效果展示了如何通过纯CSS和少量JavaScript来创建复杂、富有动感的用户界面元素。它不仅功能性强,还具有很高的视觉吸引力,为用户提供了愉悦的交互体验。 主要特点和核心实现原理如下: 视觉效果: 一个圆形的切换按钮,模拟了日夜交替的场景。 日间模式显示太阳、云朵和一个小飞机。 夜间模式显示月亮、星星和一个宇航员熊。 切换时有平滑的动画过渡效果。 核心实现原理: a. HTML结构: b. CSS技术: c. JavaScript: 根据复选框状态同步更新body的data-dark-mode属性。 使用CSS变量(如 --dark)控制日/夜模式的状态。 大量使用CSS transitions和animations实现平滑的动画效果。 使用transform属性(如translate、scale、rotate)实现元素的移动和变形。 使用clip-path和overflow控制元素的可见区域。 巧妙运用z-index和定位来管理层叠关系。 使用SVG创建复杂的图形元素(如云、星星、熊等)。 使用嵌套的span元素组织不同的视觉元素。 关键技巧: 使用CSS变量和calc()函数动态计算样式值,实现平滑过渡。 利用SVG的灵活性创建复杂的图形和动画。 通过改变单一CSS变量(--dark)来控制整个场景的变化。 创作来源:Jhey - Night && Day Toggle ☀️/🌙 详细代码解析:与众不同的夜间开关交互效果源代码 可上下滑动查看完整源代码: <!DOCTYPE html> <html lang="en"> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> *, *:after, *:before { box-sizing: border-box; } :root { --slide-ease: cubic-bezier(.4, -0.3, .6, 1.3); --easing: var(--slide-ease); --speed: 0.5s; --width: clamp(200px, 45vmin, 500px); --ar: 8 / 3; --ray: hsl(0 0% 100% / 0.5); --sun: hsl(47, 91%, 58%); --moon: hsl(212, 13%, 82%); --crater: hsl(221, 16%, 68%); --bg: hsl(219, 30%, 88%); --bear-speed: 10s; --color: hsl(219 30% 20%); } [data-dark-mode=true] { --bg: hsl(219, 30%, 12%); --color: hsl(219 30% 98%); } body { display: grid; place-items: center; min-height: 100vh; overflow: hidden; background: var(--bg); transition: background var(--speed) var(--easing); font-family: sans-serif, system-ui; } .toggle__backdrop:first-of-type .clouds path:first-of-type { fill: var(--ray); } .toggle { -webkit-tap-highlight-color: transparent; width: var(--width); /* random attempts at tackling the overflow iOS issue */ z-index: 10; will-change: transform; isolation: isolate; transform: translate3d(0, 0, 0); /* End of workaround city */ aspect-ratio: var(--ar); border-radius: 100vh; border: 0; position: relative; padding: 0; overflow: hidden; cursor: pointer; transition: background var(--speed) var(--easing); --sky: hsl(204, 53%, 47%); --night: hsl(229, 25%, 16%); outline-color: transparent; background: hsl(calc(204 + (var(--dark, 0) * 25)) calc((53 - (var(--dark, 0) * 28)) * 1%) calc((47 - (var(--dark, 0) * 31)) * 1%)); box-shadow: calc(var(--width) * 0) calc(var(--width) * 0.02) calc(var(--width) * 0.01) calc(var(--width) * -0.0025) hsl(210 10% 100% / 0.95), calc(var(--width) * 0) calc(var(--width) * -0.02) calc(var(--width) * 0.01) calc(var(--width) * -0.0025) hsl(210 10% 10% / 0.2), calc(var(--width) * 0) calc(var(--width) * 0.02) calc(var(--width) * 0.5) 0 hsl(210 10% 100% / 0.15); } .toggle:after { content: ""; position: absolute; inset: 0; box-shadow: calc(var(--width) * 0) calc(var(--width) * -0.025) calc(var(--width) * 0.025) 0 hsl(210 10% 10% / 0.15) inset, calc(var(--width) * 0) calc(var(--width) * 0.025) calc(var(--width) * 0.025) 0 hsl(210 10% 10% / 0.65) inset; border-radius: 100vh; } .toggle__content { position: absolute; top: 0; left: 0; right: 0; bottom: 0; overflow: hidden; border-radius: 100vh; display: block; clip-path: inset(0 0 0 0 round 100vh); } .toggle__backdrop { overflow: visible !important; position: absolute; bottom: 0; width: 100%; left: 0; transition: translate var(--speed) var(--easing); translate: 0 calc(var(--dark, 0) * (100% - (3 / 8 * var(--width)))); } [aria-pressed=false] .toggle__backdrop:last-of-type { transition-timing-function: cubic-bezier(.2, -0.6, .7, 1.6); } [aria-pressed=false] .stars path { transition-delay: 0s; } .stars path { transform-box: fill-box; transform-origin: 25% 50%; scale: calc(0.25 + (var(--dark, 0) * 0.75)); transition: scale var(--speed) calc(var(--speed) * 0.5) var(--easing); } .toggle__indicator { height: 100%; aspect-ratio: 1; border-radius: 0%; display: grid; place-items: center; padding: 3%; } .pilot-bear { position: absolute; width: 25%; } .toggle__star { height: 100%; aspect-ratio: 1; border-radius: 50%; position: relative; transition: translate var(--speed) var(--easing); translate: calc((var(--dark, 0) * -10%) + 5%) 0; /* translate: calc((var(--dark, 0) * -18%) + 5%) 0; */ } .sun { background: var(--sun); position: absolute; inset: 0; border-radius: 50%; overflow: hidden; box-shadow: calc(var(--width) * 0.01) calc(var(--width) * 0.01) calc(var(--width) * 0.02) 0 hsl(210 10% 100% / 0.95) inset, calc(var(--width) * -0.01) calc(var(--width) * -0.01) calc(var(--width) * 0.02) 0 hsl(210 10% 20% / 0.5) inset; } .moon { position: absolute; inset: -1%; border-radius: 50%; background: var(--moon); transition: translate var(--speed) ease-in-out; translate: calc((100 - (var(--dark, 0) * 100)) * 1%) 0%; box-shadow: calc(var(--width) * 0.01) calc(var(--width) * 0.01) calc(var(--width) * 0.02) 0 hsl(210 10% 100% / 0.95) inset, calc(var(--width) * -0.01) calc(var(--width) * -0.01) calc(var(--width) * 0.02) 0 hsl(210 10% 10% / 0.95) inset; } .moon__crater { position: absolute; background: var(--crater); border-radius: 50%; width: calc(var(--size, 10) * 1%); aspect-ratio: 1; left: calc(var(--x) * 1%); top: calc(var(--y) * 1%); box-shadow: calc(var(--width) * 0.01) calc(var(--width) * 0.01) calc(var(--width) * 0.01) 0 hsl(210 10% 6% / 0.25) inset, 0 calc(var(--width) * 0.005) calc(var(--width) * 0.01) 0 hsl(210 10% 100% / 0.25); } .moon__crater:nth-of-type(1) { --size: 18; --x: 40; --y: 15; } .moon__crater:nth-of-type(2) { --size: 20; --x: 65; --y: 58; } .moon__crater:nth-of-type(3) { --size: 34; --x: 18; --y: 40; } .toggle__star:before { content: ""; z-index: -1; width: 356%; background: radial-gradient(hsl(0 0% 100% / 0.25) 40%, transparent 40.5%), radial-gradient(hsl(0 0% 100% / 0.25) 56%, transparent 56.5%) hsl(0 0% 100% / 0.25); border-radius: 50%; aspect-ratio: 1; position: absolute; top: 50%; left: 50%; transition: translate var(--speed) var(--easing); translate: calc((50 - (var(--dark, 0) * 4)) * -1%) -50%; } .toggle__star:after { content: ""; position: absolute; inset: 0; display: block; background: hsl(0 0% 0% / 0.5); filter: blur(4px); translate: 2% 4%; border-radius: 50%; z-index: -1; } .toggle__indicator-wrapper { position: absolute; inset: 0; transition: translate var(--speed) var(--slide-ease); translate: calc(var(--dark, 0) * (var(--width) - (3 / 8 * var(--width)))) 0; } [aria-pressed=true] { --dark: 1; } /* Fun stuff! */ /* We have 11 stars */ .stars g { transform-box: fill-box; transform-origin: 50% 50%; } .stars g:nth-of-type(3) { animation: twinkle 4s -2s infinite; } .stars g:nth-of-type(11) { animation: twinkle 6s -2s infinite; } .stars g:nth-of-type(9) { animation: twinkle 4s -1s infinite; } @keyframes twinkle { 0%, 40%, 60%, 100% { transform: scale(1); } 50% { transform: scale(0); } } .astrobear { width: 12%; position: absolute; top: 100%; left: 0%; transition: translate calc(var(--speed) + (var(--dark, 0) * (var(--bear-speed) - var(--speed)))) calc(var(--bear-speed) * (0.4 * var(--dark, 0))) linear; translate: calc(var(--dark, 0) * 400%) calc(var(--dark, 0) * -350%); } .astrobear svg { transform-origin: 50% 75%; scale: var(--dark, 0); rotate: calc(var(--dark, 0) * 360deg); transition: rotate calc(var(--speed) + (var(--dark, 0) * (var(--bear-speed) - var(--speed)))) calc(var(--bear-speed) * 0.4) linear, scale var(--speed) ease-in-out; } .astrobear__container { position: absolute; overflow: hidden; inset: 0; clip-path: inset(0 0 0 0); opacity: var(--dark, 0); translate: 0 calc(-200% + (var(--dark, 0) * 200%)); transition: opacity var(--speed) var(--easing), translate var(--speed) var(--easing); } .pilot__container { position: absolute; overflow: hidden; inset: 0; clip-path: inset(0 0 0 0); opacity: calc(1 - var(--dark, 0)); translate: 0 calc(var(--dark, 0) * 200%); transition: opacity var(--speed) var(--easing), translate var(--speed) var(--easing); } .pilot-bear { width: 18%; position: absolute; top: 70%; left: 100%; transition: translate calc(var(--speed) + ((1 - var(--dark, 0)) * ((var(--bear-speed) * 0.5) - var(--speed)))) calc((var(--bear-speed) * 0.5) * ((1 - var(--dark, 0)) * 0.4)) linear; translate: calc((0 - (1 - var(--dark, 0))) * (var(--width) + 100%)) calc((0 - (1 - var(--dark, 0))) * (200%)); } .pilot { rotate: 12deg; animation: fly 4s infinite ease-in-out; width: 100%; } @keyframes fly { 50% { translate: 0 -25%; } } .controls { position: fixed; bottom: 1rem; right: 1rem; display: flex; align-items: center; gap: 0.5rem; font-family: sans-serif; color: var(--color); transition: color var(--speed) var(--easing); } [type=checkbox] { accent-color: var(--color); transition: accent-color var(--speed) var(--easing); } </style> </head> <body> <div class="controls"> <label for="sync">Sync <body></label> <input id="sync" type="checkbox" /> </div> <!-- 背景色控制 --> <button class="toggle" aria-pressed="false" title="Toggle Dark Mode"> <span class="toggle__content"> <!-- light模式的云2 --> <svg aria-hidden=true class="toggle__backdrop" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 290 228"> <g class="clouds"> <path fill="#D9D9D9" d="M335 147.5c0 27.89-22.61 50.5-50.5 50.5a50.78 50.78 0 0 1-9.29-.853c-2.478 12.606-10.595 23.188-21.615 29.011C245.699 243.749 228.03 256 207.5 256a50.433 50.433 0 0 1-16.034-2.599A41.811 41.811 0 0 1 166 262a41.798 41.798 0 0 1-22.893-6.782A42.21 42.21 0 0 1 135 256a41.82 41.82 0 0 1-19.115-4.592A41.84 41.84 0 0 1 88 262c-1.827 0-3.626-.117-5.391-.343C74.911 270.448 63.604 276 51 276c-23.196 0-42-18.804-42-42s18.804-42 42-42c1.827 0 3.626.117 5.391.343C64.089 183.552 75.396 178 88 178a41.819 41.819 0 0 1 19.115 4.592C114.532 176.002 124.298 172 135 172a41.798 41.798 0 0 1 22.893 6.782 42.066 42.066 0 0 1 7.239-.773C174.137 164.159 189.749 155 207.5 155c.601 0 1.199.01 1.794.031A41.813 41.813 0 0 1 234 147h.002c.269-27.66 22.774-50 50.498-50 27.89 0 50.5 22.61 50.5 50.5Z" /> </g> </svg> <!-- light模式的小飞机 --> <span aria-hidden=true class="pilot__container"> <span class="pilot-bear"> <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f13edbe752dd4d11b7c394aaf0f68637~tplv-k3u1fbpfcp-watermark.image?" alt="pilot-bear" class="pilot" /> </span> </span> <!-- light模式的云1 --> <svg aria-hidden=true class="toggle__backdrop" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 290 228"> <g class="clouds"> <path fill="#fff" d="M328 167.5c0 15.214-7.994 28.56-20.01 36.068.007.31.01.621.01.932 0 23.472-19.028 42.5-42.5 42.5-3.789 0-7.461-.496-10.957-1.426C249.671 263.676 233.141 277 213.5 277a42.77 42.77 0 0 1-7.702-.696C198.089 284.141 187.362 289 175.5 289a42.338 42.338 0 0 1-27.864-10.408A42.411 42.411 0 0 1 133.5 281c-4.36 0-8.566-.656-12.526-1.876C113.252 287.066 102.452 292 90.5 292a42.388 42.388 0 0 1-15.8-3.034A42.316 42.316 0 0 1 48.5 298C25.028 298 6 278.972 6 255.5S25.028 213 48.5 213a42.388 42.388 0 0 1 15.8 3.034A42.316 42.316 0 0 1 90.5 207c4.36 0 8.566.656 12.526 1.876C110.748 200.934 121.548 196 133.5 196a42.338 42.338 0 0 1 27.864 10.408A42.411 42.411 0 0 1 175.5 204c2.63 0 5.204.239 7.702.696C190.911 196.859 201.638 192 213.5 192c3.789 0 7.461.496 10.957 1.426 2.824-10.491 9.562-19.377 18.553-24.994-.007-.31-.01-.621-.01-.932 0-23.472 19.028-42.5 42.5-42.5s42.5 19.028 42.5 42.5Z" /> </g> </svg> <span class="toggle__indicator-wrapper"> <span class="toggle__indicator"> <span class="toggle__star"> <span class="sun"> <span class="moon"> <span class="moon__crater"></span> <span class="moon__crater"></span> <span class="moon__crater"></span> </span> </span> </span> </span> </span> <!-- dark模式的星星 --> <svg aria-hidden=true class="toggle__backdrop" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 290 228"> <g> <g class="stars"> <g> <path fill="#fff" fill-rule="evenodd" d="M61 11.5a.75.75 0 0 1 .721.544l.813 2.846a3.75 3.75 0 0 0 2.576 2.576l2.846.813a.75.75 0 0 1 0 1.442l-2.846.813a3.749 3.749 0 0 0-2.576 2.576l-.813 2.846a.75.75 0 0 1-1.442 0l-.813-2.846a3.749 3.749 0 0 0-2.576-2.576l-2.846-.813a.75.75 0 0 1 0-1.442l2.846-.813a3.749 3.749 0 0 0 2.576-2.576l.813-2.846A.75.75 0 0 1 61 11.5Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M62.5 45.219a.329.329 0 0 1 .315.238l.356 1.245a1.641 1.641 0 0 0 1.127 1.127l1.245.356a.328.328 0 0 1 0 .63l-1.245.356a1.641 1.641 0 0 0-1.127 1.127l-.356 1.245a.328.328 0 0 1-.63 0l-.356-1.245a1.641 1.641 0 0 0-1.127-1.127l-1.245-.356a.328.328 0 0 1 0-.63l1.245-.356a1.641 1.641 0 0 0 1.127-1.127l.356-1.245a.328.328 0 0 1 .315-.238Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M32 31.188a.28.28 0 0 1 .27.204l.305 1.067a1.405 1.405 0 0 0 .966.966l1.068.305a.28.28 0 0 1 0 .54l-1.068.305a1.405 1.405 0 0 0-.966.966l-.305 1.068a.28.28 0 0 1-.54 0l-.305-1.068a1.406 1.406 0 0 0-.966-.966l-1.067-.305a.28.28 0 0 1 0-.54l1.067-.305a1.406 1.406 0 0 0 .966-.966l.305-1.068a.281.281 0 0 1 .27-.203Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M41.5 74.219a.329.329 0 0 1 .315.238l.356 1.245a1.641 1.641 0 0 0 1.127 1.127l1.245.356a.328.328 0 0 1 0 .63l-1.245.356a1.641 1.641 0 0 0-1.127 1.127l-.356 1.245a.328.328 0 0 1-.63 0l-.356-1.245a1.641 1.641 0 0 0-1.127-1.127l-1.245-.356a.328.328 0 0 1 0-.63l1.245-.356a1.641 1.641 0 0 0 1.127-1.127l.356-1.245a.328.328 0 0 1 .315-.238Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M34 83.188a.28.28 0 0 1 .27.203l.305 1.068a1.405 1.405 0 0 0 .966.966l1.068.305a.28.28 0 0 1 0 .54l-1.068.305a1.405 1.405 0 0 0-.966.966l-.305 1.068a.28.28 0 0 1-.54 0l-.305-1.068a1.406 1.406 0 0 0-.966-.966l-1.068-.305a.28.28 0 0 1 0-.54l1.068-.305a1.406 1.406 0 0 0 .966-.966l.305-1.068a.281.281 0 0 1 .27-.204Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M63 89.25a.375.375 0 0 1 .36.272l.407 1.423a1.874 1.874 0 0 0 1.288 1.288l1.423.406a.374.374 0 0 1 0 .722l-1.423.406a1.874 1.874 0 0 0-1.288 1.288l-.407 1.423a.374.374 0 0 1-.72 0l-.407-1.423a1.874 1.874 0 0 0-1.288-1.288l-1.423-.406a.374.374 0 0 1 0-.722l1.423-.406a1.874 1.874 0 0 0 1.288-1.288l.407-1.423a.376.376 0 0 1 .36-.272Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M110.5 53.156a.236.236 0 0 1 .225.17l.254.89a1.174 1.174 0 0 0 .805.805l.89.254a.23.23 0 0 1 .122.084.233.233 0 0 1-.122.366l-.89.254a1.167 1.167 0 0 0-.805.805l-.254.89a.232.232 0 0 1-.225.17.235.235 0 0 1-.225-.17l-.254-.89a1.174 1.174 0 0 0-.805-.805l-.89-.254a.23.23 0 0 1-.122-.084.233.233 0 0 1 .122-.366l.89-.254a1.167 1.167 0 0 0 .805-.805l.254-.89a.232.232 0 0 1 .225-.17Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M120 27.188a.279.279 0 0 1 .27.204l.305 1.067a1.41 1.41 0 0 0 .966.966l1.067.305a.283.283 0 0 1 .148.1.286.286 0 0 1 0 .34.283.283 0 0 1-.148.1l-1.067.305a1.403 1.403 0 0 0-.966.966l-.305 1.067a.279.279 0 0 1-.439.147.275.275 0 0 1-.101-.147l-.305-1.067a1.41 1.41 0 0 0-.966-.966l-1.068-.305a.284.284 0 0 1-.147-.1.286.286 0 0 1 0-.34.284.284 0 0 1 .147-.1l1.068-.305a1.405 1.405 0 0 0 .966-.966l.305-1.067a.279.279 0 0 1 .27-.204Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M155 28.5a.753.753 0 0 1 .721.544l.813 2.846a3.746 3.746 0 0 0 2.576 2.576l2.846.813a.747.747 0 0 1 .543.721.75.75 0 0 1-.543.721l-2.846.813a3.75 3.75 0 0 0-2.576 2.576l-.813 2.846a.747.747 0 0 1-.721.543.749.749 0 0 1-.721-.543l-.813-2.846a3.746 3.746 0 0 0-2.576-2.576l-2.846-.813a.747.747 0 0 1-.543-.721.75.75 0 0 1 .543-.721l2.846-.813a3.75 3.75 0 0 0 2.576-2.576l.813-2.846A.751.751 0 0 1 155 28.5Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M147 60.25a.377.377 0 0 1 .36.272l.407 1.423a1.883 1.883 0 0 0 1.288 1.288l1.423.407a.375.375 0 0 1 0 .72l-1.423.407a1.875 1.875 0 0 0-1.288 1.288l-.407 1.423a.371.371 0 0 1-.36.272.377.377 0 0 1-.36-.272l-.407-1.423a1.883 1.883 0 0 0-1.288-1.288l-1.423-.406a.375.375 0 0 1 0-.722l1.423-.406a1.875 1.875 0 0 0 1.288-1.288l.407-1.423a.372.372 0 0 1 .36-.272Z" clip-rule="evenodd" /> </g> <g> <path fill="#fff" fill-rule="evenodd" d="M125.5 76.344a.513.513 0 0 1 .496.374l.559 1.956a2.574 2.574 0 0 0 1.771 1.771l1.956.56a.514.514 0 0 1 .27.805.514.514 0 0 1-.27.186l-1.956.559a2.57 2.57 0 0 0-1.771 1.77l-.559 1.957a.514.514 0 0 1-.806.27.514.514 0 0 1-.186-.27l-.559-1.956a2.574 2.574 0 0 0-1.771-1.771l-1.956-.56a.514.514 0 0 1-.27-.805.514.514 0 0 1 .27-.186l1.956-.559a2.57 2.57 0 0 0 1.771-1.77l.559-1.957a.515.515 0 0 1 .496-.374Z" clip-rule="evenodd" /> </g> </g> </g> </svg> <!-- 太空熊动画 --> <span class="astrobear__container"> <span class="astrobear"> <svg aria-hidden=true xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 316 432"> <circle cx="158" cy="143" r="140" fill="#444" /> <circle cx="158" cy="143" r="140" fill="url(#a)" fill-opacity=".2" /> <circle cx="158" cy="143" r="140" stroke="#000" stroke-width="6" /> <path fill="#AF7128" fill-rule="evenodd" d="M65.98 159.61C49.913 155.643 38 141.134 38 123.842 38 103.495 54.495 87 74.842 87c14.337 0 26.761 8.19 32.85 20.146C119.687 100.674 133.414 97 148 97h20c14.52 0 28.19 3.641 40.146 10.059C214.251 95.15 226.65 87 240.952 87c20.347 0 36.842 16.495 36.842 36.842 0 17.222-11.818 31.685-27.787 35.72A85.104 85.104 0 0 1 253 182v66.56l10.054-10.054c11.325-11.325 29.687-11.325 41.012 0s11.325 29.687 0 41.012l-44.548 44.548a29.004 29.004 0 0 1-6.518 4.906V407c0 12.15-9.85 22-22 22h-44c-12.15 0-22-9.85-22-22v-28.69a41.072 41.072 0 0 1-14 .174V407c0 12.15-9.85 22-22 22H85c-12.15 0-22-9.85-22-22v-77.797a28.99 28.99 0 0 1-6.946-5.137l-44.548-44.548c-11.325-11.325-11.325-29.687 0-41.012 11.326-11.325 29.687-11.325 41.013 0L63 248.988V182a85.106 85.106 0 0 1 2.98-22.39Z" clip-rule="evenodd" /> <path fill="#000" d="m65.98 159.61 2.894.789a3.002 3.002 0 0 0-2.175-3.701l-.72 2.912Zm41.712-52.464-2.673 1.362a3 3 0 0 0 4.098 1.279l-1.425-2.641Zm100.454-.087-1.419 2.643a3 3 0 0 0 4.089-1.274l-2.67-1.369Zm41.861 52.503-.735-2.908a2.997 2.997 0 0 0-2.159 3.698l2.894-.79ZM253 248.56h-3a3 3 0 0 0 5.121 2.121L253 248.56Zm10.054-10.054-2.121-2.121 2.121 2.121Zm41.012 0-2.121 2.122 2.121-2.122Zm0 41.012 2.121 2.122-2.121-2.122ZM253 328.972l-1.448-2.627a3 3 0 0 0-1.552 2.627h3Zm-88 49.338h3a3 3 0 0 0-3.548-2.949l.548 2.949Zm-14 .174.475-2.963a3 3 0 0 0-3.475 2.963h3Zm-88-49.281h3a3 3 0 0 0-1.597-2.651L63 329.203Zm-6.946-5.137-2.121 2.121 2.121-2.121Zm-44.548-44.548-2.121 2.122 2.121-2.122Zm0-41.012 2.122 2.122-2.122-2.122Zm41.013 0-2.122 2.122 2.122-2.122ZM63 248.988l-2.121 2.121A2.999 2.999 0 0 0 66 248.988h-3ZM35 123.842c0 18.704 12.886 34.391 30.26 38.681l1.439-5.825C51.941 153.054 41 139.721 41 123.842h-6ZM74.842 84C52.838 84 35 101.838 35 123.842h6C41 105.151 56.151 90 74.842 90v-6Zm35.524 21.785C103.785 92.862 90.351 84 74.842 84v6c13.165 0 24.58 7.517 30.177 18.508l5.347-2.723ZM148 94c-15.095 0-29.311 3.803-41.733 10.506l2.85 5.281C120.685 103.544 133.924 100 148 100v-6Zm20 0h-20v6h20v-6Zm41.565 10.416C197.183 97.769 183.027 94 168 94v6c14.013 0 27.196 3.512 38.727 9.702l2.838-5.286ZM240.952 84c-15.471 0-28.878 8.82-35.476 21.691l5.34 2.737C216.427 97.481 227.819 90 240.952 90v-6Zm39.842 39.842c0-22.004-17.838-39.842-39.842-39.842v6c18.69 0 33.842 15.151 33.842 33.842h6Zm-30.052 38.629c17.269-4.364 30.052-20 30.052-38.629h-6c0 15.816-10.853 29.104-25.522 32.812l1.47 5.817ZM256 182a88.09 88.09 0 0 0-3.099-23.228l-5.788 1.58A82.082 82.082 0 0 1 250 182h6Zm0 66.56V182h-6v66.56h6Zm-.879 2.121 10.054-10.053-4.242-4.243-10.054 10.054 4.242 4.242Zm10.054-10.053c10.154-10.154 26.616-10.154 36.77 0l4.242-4.243c-12.496-12.497-32.758-12.497-45.254 0l4.242 4.243Zm36.77 0c10.153 10.153 10.153 26.615 0 36.769l4.242 4.243c12.497-12.497 12.497-32.758 0-45.255l-4.242 4.243Zm0 36.769-44.548 44.548 4.243 4.242 44.547-44.547-4.242-4.243Zm-44.548 44.548a26.013 26.013 0 0 1-5.845 4.4l2.896 5.255a32.006 32.006 0 0 0 7.192-5.413l-4.243-4.242ZM256 360v-31.028h-6V360h6Zm0 47v-47h-6v47h6Zm-25 25c13.807 0 25-11.193 25-25h-6c0 10.493-8.506 19-19 19v6Zm-44 0h44v-6h-44v6Zm-25-25c0 13.807 11.193 25 25 25v-6c-10.493 0-19-8.507-19-19h-6Zm0-28.69V407h6v-28.69h-6Zm-4.5 3.69c2.74 0 5.429-.253 8.048-.74l-1.096-5.899c-2.261.42-4.583.639-6.952.639v6Zm-6.975-.554c2.279.365 4.608.554 6.975.554v-6c-2.047 0-4.058-.163-6.025-.479l-.95 5.925ZM154 407v-28.516h-6V407h6Zm-25 25c13.807 0 25-11.193 25-25h-6c0 10.493-8.506 19-19 19v6Zm-44 0h44v-6H85v6Zm-25-25c0 13.807 11.193 25 25 25v-6c-10.493 0-19-8.507-19-19h-6Zm0-47v47h6v-47h-6Zm0-30.797V360h6v-30.797h-6Zm-6.067-3.016a32.008 32.008 0 0 0 7.664 5.668l2.806-5.303a26.002 26.002 0 0 1-6.228-4.607l-4.242 4.242ZM9.385 281.64l44.548 44.547 4.242-4.242-44.547-44.548-4.243 4.243Zm0-45.255c-12.497 12.497-12.497 32.758 0 45.255l4.243-4.243c-10.154-10.154-10.154-26.616 0-36.769l-4.243-4.243Zm45.255 0c-12.497-12.497-32.758-12.497-45.255 0l4.243 4.243c10.153-10.154 26.616-10.154 36.77 0l4.242-4.243Zm10.481 10.481L54.64 236.385l-4.243 4.243 10.482 10.481 4.242-4.243ZM60.001 182v66.988h6V182h-6Zm3.084-23.178A88.1 88.1 0 0 0 60 182h6c0-7.48 1.001-14.722 2.874-21.601l-5.789-1.577Z" /> <path fill="#fff" fill-rule="evenodd" d="M267.721 234.712C241.49 266.061 202.073 286 158 286c-43.749 0-82.91-19.647-109.141-50.598-11.328-8.104-27.18-7.069-37.353 3.104-11.325 11.325-11.325 29.687 0 41.012l44.548 44.548A28.99 28.99 0 0 0 63 329.203V407c0 12.15 9.85 22 22 22h44c12.15 0 22-9.85 22-22v-28.517c2.123.341 4.293.517 6.5.517 2.555 0 5.06-.236 7.5-.69V407c0 12.15 9.85 22 22 22h44c12.15 0 22-9.85 22-22V328.972a29.004 29.004 0 0 0 6.518-4.906l44.548-44.548c11.325-11.325 11.325-29.687 0-41.012-9.875-9.875-25.099-11.139-36.345-3.794Z" clip-rule="evenodd" /> <path fill="url(#b)" fill-opacity=".5" fill-rule="evenodd" d="M267.721 234.712C241.49 266.061 202.073 286 158 286c-43.749 0-82.91-19.647-109.141-50.598-11.328-8.104-27.18-7.069-37.353 3.104-11.325 11.325-11.325 29.687 0 41.012l44.548 44.548A28.99 28.99 0 0 0 63 329.203V407c0 12.15 9.85 22 22 22h44c12.15 0 22-9.85 22-22v-28.517c2.123.341 4.293.517 6.5.517 2.555 0 5.06-.236 7.5-.69V407c0 12.15 9.85 22 22 22h44c12.15 0 22-9.85 22-22V328.972a29.004 29.004 0 0 0 6.518-4.906l44.548-44.548c11.325-11.325 11.325-29.687 0-41.012-9.875-9.875-25.099-11.139-36.345-3.794Z" clip-rule="evenodd" /> <path fill="#000" d="m267.721 234.712-1.64-2.511c-.248.162-.47.359-.66.586l2.3 1.925Zm-218.862.69 2.289-1.94a3.026 3.026 0 0 0-.544-.5l-1.745 2.44Zm-37.353 3.104 2.122 2.121-2.122-2.121Zm0 41.012-2.121 2.122 2.121-2.122Zm44.548 44.548-2.121 2.121 2.121-2.121ZM63 329.203h3a2.999 2.999 0 0 0-1.597-2.651L63 329.203Zm88 49.28.475-2.962a3 3 0 0 0-3.475 2.962h3Zm14-.173h3a3 3 0 0 0-3.548-2.949l.548 2.949Zm88-49.338-1.448-2.627a3 3 0 0 0-1.552 2.627h3Zm51.066-49.454 2.121 2.122-2.121-2.122Zm0-41.012-2.121 2.121 2.121-2.121Zm-38.645-5.719C239.735 263.484 201.148 283 158 283v6c44.999 0 85.245-20.361 112.022-52.362l-4.601-3.851ZM158 283c-42.831 0-81.167-19.23-106.852-49.538l-4.578 3.879C73.347 268.937 113.332 289 158 289v-6ZM13.628 240.627c9.118-9.118 23.331-10.049 33.486-2.785l3.49-4.88c-12.502-8.944-29.991-7.805-41.219 3.423l4.243 4.242Zm0 36.77c-10.154-10.154-10.154-26.616 0-36.77l-4.243-4.242c-12.497 12.497-12.497 32.758 0 45.255l4.243-4.243Zm44.547 44.548-44.547-44.548-4.243 4.243 44.548 44.547 4.242-4.242Zm6.228 4.607a26.002 26.002 0 0 1-6.228-4.607l-4.242 4.242a32.008 32.008 0 0 0 7.664 5.668l2.806-5.303ZM66 360v-30.797h-6V360h6Zm0 47v-47h-6v47h6Zm19 19c-10.493 0-19-8.507-19-19h-6c0 13.807 11.193 25 25 25v-6Zm44 0H85v6h44v-6Zm19-19c0 10.493-8.506 19-19 19v6c13.807 0 25-11.193 25-25h-6Zm0-28.517V407h6v-28.517h-6Zm9.5-2.483c-2.047 0-4.058-.163-6.025-.479l-.95 5.925c2.279.365 4.608.554 6.975.554v-6Zm6.952-.639c-2.261.42-4.583.639-6.952.639v6c2.74 0 5.429-.253 8.048-.74l-1.096-5.899ZM168 407v-28.69h-6V407h6Zm19 19c-10.493 0-19-8.507-19-19h-6c0 13.807 11.193 25 25 25v-6Zm44 0h-44v6h44v-6Zm19-19c0 10.493-8.506 19-19 19v6c13.807 0 25-11.193 25-25h-6Zm0-47v47h6v-47h-6Zm0-31.028V360h6v-31.028h-6Zm7.397-7.027a26.043 26.043 0 0 1-5.845 4.4l2.896 5.255a32.036 32.036 0 0 0 7.192-5.413l-4.243-4.242Zm44.548-44.548-44.548 44.548 4.243 4.242 44.547-44.547-4.242-4.243Zm0-36.77c10.153 10.154 10.153 26.616 0 36.77l4.242 4.243c12.497-12.497 12.497-32.758 0-45.255l-4.242 4.242Zm-32.583-3.403c10.081-6.585 23.732-5.447 32.583 3.403l4.242-4.242c-10.898-10.899-27.697-12.29-40.106-4.184l3.281 5.023Z" /> <path fill="#000" d="M292.654 245.606a3 3 0 1 1-4.243-4.242l4.243 4.242Zm8.485-8.485-8.485 8.485-4.243-4.242 8.485-8.486 4.243 4.243ZM301.208 254.161a3 3 0 1 1-4.242-4.243l4.242 4.243Zm8.486-8.485-8.486 8.485-4.242-4.243 8.485-8.485 4.243 4.243ZM21.919 246.606a3 3 0 1 0 4.242-4.242l-4.242 4.242Zm-8.486-8.485 8.486 8.485 4.242-4.242-8.485-8.486-4.243 4.243ZM13.364 255.161a3 3 0 1 0 4.243-4.243l-4.243 4.243Zm-8.485-8.485 8.485 8.485 4.243-4.243-8.486-8.485-4.242 4.243Z" /> <path fill="#FF1E1E" d="M113.322 154.665h88.371v13.25h-88.371z" /> <path fill="#000" fill-rule="evenodd" d="M225.3 113.481c17.939 14.394 28.018 37.148 28.018 57.504H191.67c-.087-13.669-11.194-24.723-24.883-24.723h-18.56c-13.689 0-24.796 11.054-24.883 24.723H62c0-20.356 10.078-43.11 28.018-57.504C107.957 99.087 132.289 91 157.659 91c25.37 0 49.701 8.087 67.641 22.481Z" clip-rule="evenodd" /> <circle cx="212.665" cy="197.079" r="8.079" fill="#000" /> <circle cx="104.079" cy="197.079" r="8.079" fill="#000" /> <path fill="#000" d="M179.165 211.683c0 8.21-9.868 17.451-20.845 17.451-10.977 0-20.845-9.241-20.845-17.451 0-8.211 9.868-12.281 20.845-12.281 10.977 0 20.845 4.07 20.845 12.281Z" /> <path stroke="#000" stroke-linecap="round" stroke-width="6" d="M198 417v12M222 417v12M95 417v12M119 417v12" /> <circle cx="158" cy="143" r="140" fill="url(#c)" stroke="#000" stroke-width="6" /> <g clip-path="url(#d)"> <path fill="#F5D949" stroke="#000" stroke-width="6" d="m217.543 305.943.704 1.692 1.826.146 12.818 1.027h.001a.17.17 0 0 1 .059.011l.002.001a.147.147 0 0 1 .037.065.15.15 0 0 1 .008.075l-.001.002c0 .001-.01.017-.041.044h-.001l-9.765 8.365-1.391 1.192.425 1.782 2.981 12.506h.001c.009.04.008.058.008.06l-.001.002a.148.148 0 0 1-.05.056.146.146 0 0 1-.069.031h-.002c-.002-.001-.02-.005-.054-.026l-10.974-6.702-1.564-.955-1.564.955-10.974 6.702a.177.177 0 0 1-.053.025l-.002.001c-.004-.001-.032-.005-.069-.032a.146.146 0 0 1-.051-.056l-.001-.002s-.001-.018.008-.058l.001-.001 2.981-12.506.425-1.782-1.391-1.192-9.765-8.365h-.001c-.031-.027-.04-.043-.041-.044l-.001-.002a.15.15 0 0 1 .008-.075.147.147 0 0 1 .037-.065l.002-.001a.17.17 0 0 1 .059-.011h.001l12.818-1.027 1.826-.146.704-1.692 4.938-11.875a.161.161 0 0 1 .028-.051l.001-.001a.146.146 0 0 1 .076-.016c.047 0 .072.013.076.016l.001.001c.001 0 .012.013.028.051l4.938 11.875Z" /> </g> <path stroke="#000" stroke-linecap="round" stroke-width="16" d="M56.884 247.116A143.01 143.01 0 0 0 158 289a143.002 143.002 0 0 0 101.116-41.884" /> <path stroke="#000" stroke-width="6" d="M65.035 404s25.382-6.618 41.965-6.5c17.059.121 43.035 7.5 43.035 7.5M164.012 403.977s25.961-5.606 42.932-5.472C224.402 398.642 251 405 251 405M25 290l7.769-4.072a57.001 57.001 0 0 0 25.067-26.121L62 251M254.363 252l3.867 7.873a57.002 57.002 0 0 0 25.452 25.746l8.694 4.394" /> <defs> <linearGradient id="a" x1="158" x2="158" y1="0" y2="286" gradientUnits="userSpaceOnUse"> <stop offset=".219" /> <stop offset="1" stop-color="#fff" /> </linearGradient> <linearGradient id="b" x1="254" x2="100" y1="419" y2="325" gradientUnits="userSpaceOnUse"> <stop stop-opacity=".98" /> <stop offset="1" stop-opacity="0" /> </linearGradient> <radialGradient id="c" cx="0" cy="0" r="1" gradientTransform="matrix(165.9998 58.9999 -60.6938 170.7657 210 171)" gradientUnits="userSpaceOnUse"> <stop offset=".771" stop-color="#D9D9D9" stop-opacity="0" /> <stop offset="1" stop-color="#fff" stop-opacity=".63" /> </radialGradient> <clipPath id="d"> <path fill="#fff" d="M189 291h47v45h-47z" /> </clipPath> </defs> </svg> </span> </span> </span> </button> </body> <script> const BUTTON = document.querySelector("button"); const SYNC = document.querySelector("#sync") const TOGGLE = () => { const IS_PRESSED = BUTTON.matches("[aria-pressed=true]"); if (SYNC.checked) document.body.setAttribute("data-dark-mode", IS_PRESSED ? false : true); BUTTON.setAttribute("aria-pressed", IS_PRESSED ? false : true); }; BUTTON.addEventListener("click", TOGGLE); </script> </html>使用方式 复制源代码到空白的html格式文件,在浏览中打开运行即可。
-
一个兼容 OneBot 协议的非官方 QQ 客户端实现,支持网页及 electron 客户端 Stapxs-QQ-Lite-2.0 一个兼容 OneBot 协议的非官方 QQ 客户端实现,支持网页及 electron 客户端Github地址:隐藏内容,请前往内页查看详情 在线体验:隐藏内容,请前往内页查看详情 图片 ✨ 特性支持 ✅ 使用 Vue.js 全家桶开发,快乐前后端分离 🎨 自适应布局,竖版也能使用 🖥️ 支持 PWA(都有 Electron 了(小声)) 🌚 Light/Dark Mode 自动切换 🍱 该有的都有(虽然比不过官方端) 复杂消息显示、转发、回复、撤回 群文件、群公告、群设置(一小部分)、精华消息 图片、收藏表情、文件发送 📦️ 支持多种 bot,我就是要用! 🔥 水深火热但是更好看的 Electron 客户端 🥚 彩蛋!来更多的彩蛋! 🛠 更多特性开发中 💬 提醒和问题 > 关于不安全连接 当使用 https 页面连接 ws 服务(反之相同)的情况下,连接将会失败;这是由于其中某一者是不安全的。在这种情况下,你可以选择将 ws 提升为 wss 或者将 https 降级为 http(不安全)来解决问题,此处不提供解决方案。>> Stapxs-QQ-Lite#32 > 我能使用其他 QQ Http Bot 吗 如果它兼容 OneBot 11 协议, 你可以尝试连接它, 但是由于消息体格式和接口扩展的差异,大部分情况下都不能完全正常使用。 已经兼容的 Bot 都写在了文档里,可以去 这里 查看。 > 使用 Bot 是否有风险 如果你使用的是 oicq-http, 可以查看此处了解 如果你尝试使用其他 QQ Bot (参见上一条问题), 请自行参考它的文档。 使用风险:https://github.com/takayama-lily/oicq/wiki/98.%E5%85%B3%E4%BA%8E%E8%B4%A6%E5%8F%B7%E5%86%BB%E7%BB%93%E5%92%8C%E9%A3%8E%E6%8E%A7
-
不要过度使用 console.log 啦!我们有更好的调试方式! Hello,大家好,我是 Sunday。 在日常的工作中,我经常会帮同学【远程调试代码】。在远程时,我发现 很多同学会大量使用 console.log 进行打印调试。打印的数量之多,让同学自己都摸不清哪个打印对应哪个内容了😂。 毫无疑问,console.log 是一个很好的调试方式。但是 如果我们滥用它,效果反而会适得其反!大量打印信息堆积如山,反倒使得我们难以理清各条输出的对应逻辑! 因此,我们可以寻找可好的调试方案,来解决 console.log 过多而导致的混乱问题。 console 不止 log 没错!console 不止 log ,console 对象内部提供了很多的方法。在之前的文章中,我们提到过这一点:调试只会console.log?来看一看这 6 种惊艳的调试技巧! 使用更多的 console 方法配合可以帮助我们大幅提升调试的效率。 1. 使用 console.dir() 打印对象 console.dir() 是一个专门打印 对象 的 API。 console.dir() 方法可以显示指定 JavaScript 对象的属性列表,并以交互式的形式展现。输出结果呈现为分层列表,包含展开/折叠的三角形图标,可用于查看子对象的内容。因此,当我们打印对象时,可以使用 dir 代替 log 2. 使用 console.table() 打印数组 如果想要打印数组的话,那么 table() 是首选方法: const users = [ { id: 1, name: '张三', age: 25 }, { id: 2, name: '李四', age: 30 }, { id: 3, name: '王五', age: 35 }, ]; console.table(users);一句话:贼清晰! 图片 3. 使用 console.clear() 清理控制台 这个 API 可以帮我们解决控制台打印过多的问题。 如果我们要开启一轮新的调试,那么可以在开始前执行 console.clear(),清空之前的控制台打印。 这样,可以帮我们更加专注的针对本次的调试,而不需要被之前的打印信息所误导! 4. 使用 console.group() 控制打印组 这在 嵌套函数、递归 中非常有用,配合 console.groupEnd() 可以帮助我们完成分组打印。 比如下面的场景: function factorial(n) { console.group(`方法开始,长度(${n})`); if (n <= 1) { console.log("执行 1"); console.groupEnd(); return 1; } else { let result = n * factorial(n - 1); console.log(`执行 ${result}`); console.groupEnd(); return result; } } factorial(3);最终,打印结果如下: 图片 5. 使用 console.time() 完成计时 console.time() 配合 console.timeEnd() 经常用来处理 计时操作。 比如,我们想要测试一个函数的执行耗时,那么就可以通过这种方式完成: function processLargeData() { console.time("time"); // 模拟耗时操作 for (let i = 0; i < 1000000; i++) { Math.sqrt(i); } console.timeEnd("time"); } processLargeData();计时结果如下: 图片
-
免费的轻量级代码编辑器 HBuilderX HBuilderX 是由DCloud开发的一款面向Web前端开发者的集成开发环境(IDE),它是HBuilder的下一代产品,继承了HBuilder的诸多优点,并在此基础上进行了全面的升级。HBuilderX旨在提供更加强大、高效的开发体验,支持多种前端技术栈,包括但不限于HTML5、CSS3、JavaScript、Vue.js、React、Angular等。 功能特点 快速启动:启动速度快,能够在几秒内启动,这对于频繁切换任务的开发者来说是非常有用的特性。 智能提示:提供了丰富的代码补全和提示功能,能够提高编写代码的速度和准确性。 实时预览:支持实时预览功能,开发者可以直接在编辑器中看到代码修改的效果,无需频繁切换浏览器。 多页面编辑:支持多页面同时编辑,可以在同一个窗口中打开多个文件,方便进行对比和同步修改。 多光标操作:支持多光标编辑,允许用户一次编辑多处相同代码,提高编码效率。 HTML5/CSS3支持:内置对HTML5和CSS3的支持,包括新特性的语法高亮和代码提示。 Git集成:内置Git支持,方便开发者进行版本控制和团队协作。 代码美化:提供代码格式化功能,帮助开发者保持代码整洁。 文件压缩打包:支持JavaScript、CSS文件的压缩合并,减少HTTP请求次数,提高网页加载速度。 调试工具:拥有内置的调试工具,便于开发者进行前端代码的调试。 扩展插件:支持插件扩展,用户可以根据需要安装额外的插件来增强IDE的功能。 模板支持:内置了多种模板,包括常用的框架模板,方便快速搭建项目。 下载及安装 HBuilderX官网下载地址:https://www.dcloud.io/hbuilderx.html,可以在下载页面选择Windows或MacOS平台,Windows平台压缩包下载后解压即可运行。 HBuilderX首次运行时会出现主题和快捷键选择窗口,可以选择喜欢的主题和习惯的操作方式。点击【开始体验】按钮后,可以在HBuilderX自述文件中查看HBuilderX的特性。 图片 基本操作 语法提示:工具提供多种框架语法提示库,可以在窗口右下角进行选择。 图片 代码助手:工具提供智能的代码提示助手,可按【ctrl+I】打开代码助手,代码提示后可以按【alt+数字】选择直接选择某个项目。 图片 语法帮助:在编辑区域鼠标悬停会自动弹出语法或说明提示,按下【F1】键可以跳转到官方手册(2.5.7以上版本会在外部浏览器打开说明文档)。 图片 多光标操作:在编辑区域按【ctrl+鼠标左键】可增加一个光标,【ctrl+鼠标右键】可取消一个光标或选区。 图片 相同词操作:在编辑区域按【ctrl+shift+L】可以选择和光标位置相同的所有词同时进行编辑。 列选择:在编辑区域使用【alt+鼠标拖选】进入列选择编辑方式。 图片 Emmet支持:Emmet语法可以用缩写来动态生成所需要的结构及样式,从而极大改善HTML和CSS的编写流程。 图片 智能双击: 双击引号/括号内侧,选中引号/括号内的内容 双击逗号两侧,选择逗号前一段或后一段 双击行尾,选中该行(不含回车符) 双击连词符(-_)选中整个词 双击折叠行首内容开头,选择折叠段落 双击行首缩进,选择相同缩进的段落 双击列表符号,选择列表段落 双击Tag开头或结尾,选择整段Tag 双击属性赋值等号=,选择Html属性 双击if、function等关键字,选择整段包围区域 双击分号,选择js等语言的;分号前段落 双击css类名左侧,选择Css类 双击注释符选择注释区域 双击#选择markdown标题段落 双击语法定义符开头选择markdown图片、超链接、加粗、倾斜、代码等语法区 还有更多的操作和编辑技巧可以查看官方文档:https://hx.dcloud.net.cn 另外,软件对vue开发做了大量优化,新版本仅支持uni-app、uni-app cli、vue2-cli、vue3-cli项目中可以切换vue语法提示库;对Markdown文件编辑提供了强大支持,支持Emmet、快捷键编辑、智能粘贴、路径提示、Mermaid流程图等;对Json编辑进行了优化;支持开发小程序;可以创建uni-app项目;支持java插件、nodejs插件,并兼容了很多vscode的插件及代码块。 总之,HBuilderX非常适用于Web前端开发人员,特别是在进行HTML5相关的项目开发时非常高效。此外,由于其对多种语言的支持,也可以作为通用的轻量级开发工具使用。
-
你不知道的 7 项 CSS 新功能 CSS 一直在进步,不断推出新的功能,让开发更加强大、简单、有趣。以下是一些最新的 CSS 更新,它们不仅能优化你的工作流程,还会改变你对 CSS 的使用方式。我们也会通过一些示例帮助你理解这些功能的应用场景。 1. 无需 Flexbox 或 Grid 的快速居中对齐 还记得以前用 CSS 居中元素的“痛苦”吗? 现在,有了 align-content 属性,居中对齐变得轻松无比,无需额外的容器。 .my-element { display: block; align-content: center; }再也不用纠结是选 flexbox 还是 grid 了。 2. 更智能的 CSS 变量:@property CSS 变量非常灵活,但也存在缺陷,比如缺乏类型约束和默认值支持。 现在,@property 规则让你可以定义具有特定类型、继承规则和初始值的自定义属性。 传统做法: :root { --rotation: 45deg; } div { transform: rotate(var(--rotation)); }如果意外赋值 --rotation: blue;,代码可能会报错或行为异常。 新方法: @property --rotation { syntax: '<angle>'; inherits: false; initial-value: 0deg; } div { transform: rotate(var(--rotation)); } syntax: 限定变量值类型,比如 <angle> 表示角度。 inherits: 决定变量是否继承父级值。 initial-value: 定义默认值(这里是 0deg)。 这不仅提升了代码的健壮性,也减少了意外错误。 3. @starting-style:解决页面加载时样式闪烁问题 开发者经常遇到页面加载时内容闪烁的问题(FOUC)。 @starting-style 提供了解决方案,可以为元素设置初始样式,避免布局错乱。 @starting-style { .modal { opacity: 0; visibility: hidden; } } .modal { opacity: 1; visibility: visible; transition: opacity 0.3s ease, visibility 0.3s ease; }这样,即使在页面加载时,也能保证模态框的显示效果流畅自然,无需繁琐的内联样式。 4. 数学功能升级:更多强大的计算方法 以前,calc() 是 CSS 唯一的数学函数,但功能有限。 现在,新增了 round()、mod() 和 rem() 等强大函数,大幅扩展了计算能力。 传统方式: .element { width: calc(100% - 50px); }新功能: .box { /* 四舍五入为 3px */ margin: round(2.5px); } .stripe:nth-child(odd) { left: calc(var(--index) * 50px mod 200px); } .circle { /* 输出 1px */ width: rem(10px, 3px); }这些新增函数让复杂的布局计算变得更加灵活且直观。 5. 光明与黑暗的完美切换:light-dark() 定义明暗主题的样式曾经需要依赖媒体查询,容易导致代码重复。 现在,light-dark() 函数简化了这一过程。 传统方法: body { background-color: white; color: black; } @media (prefers-color-scheme: dark) { body { background-color: black; color: white; } }新方法: body { background-color: light-dark(white, black); color: light-dark(black, white); } 第一个值是浅色模式的样式。 第二个值是深色模式的样式。 再也不用重复代码,主题切换更加高效。 6. 表单验证新伪类::user-valid 和 :user-invalid 以前,:valid 和 :invalid 的表单验证伪类会在页面加载时触发,导致样式误判。 新伪类 :user-valid 和 :user-invalid 专注于用户交互后的状态,大幅提升体验。 新方法: input:user-valid { border-color: green; } input:user-invalid { border-color: red; }这避免了未交互前就出现错误提示的问题,让表单验证更贴合实际需求。 7. interpolate-size:更顺滑的尺寸动画 CSS 一直以来对高度、宽度等尺寸的动画支持不够友好。 现在,有了 interpolate-size 属性,尺寸动画变得更加平滑自然。 传统方法: .collapsible { overflow: hidden; max-height: 0; transition: max-height 0.3s ease-out; } .collapsible.open { max-height: 500px; }新方法: .panel { interpolate-size: height 0.5s ease; } .panel.open { height: auto; }浏览器会动态计算起始和结束尺寸,无论内容多少,动画都能流畅衔接。 总结 这些 CSS 新功能都得到了主流浏览器的支持,无论是 Chrome、Firefox 还是 Safari,都可以立即开始使用。掌握这些特性不仅能提升你的工作效率,还能让代码更加简洁优雅。