当前位置: 首页 > news >正文

前端炫酷动画--文字(二)

目录

一、弧形边框选项卡

二、零宽字符

三、目录滚动时自动高亮

四、高亮关键字

五、文字描边

六、按钮边框的旋转动画

七、视频文字特效

八、立体文字特效+让文字立起来

九、文字连续光影特效

十、重复渐变的边框

十一、磨砂玻璃效果

十二、FLIP动画

一、弧形边框选项卡

 <style>.tab {width: 150px;height: 40px;color: #fff;text-align: center;line-height: 40px;margin: 0 auto;background: #ed6a5e;border-radius: 10px 10px 0 0;position: relative;transform: perspective(30px) rotateX(13deg);transform-origin: center bottom; /* 以tab最下方的线为中心进行旋转 */}.tab::before,.tab::after {content: "";position: absolute;width: 10px;height: 10px;bottom: 0;background: #000;}.tab::before {left: -10px;background: radial-gradient(circle at 0 0,transparent 10px,#ed6a5e 10px); /* 左边以左上角为圆点进行径向渐变 */}.tab::after {right: -10px;background: radial-gradient(circle at 100% 0,transparent 10px,#ed6a5e 10px); /* 右边以右上角为圆点进行径向渐变 */}</style>

二、零宽字符

    <script>// 判断字符串中是否包含零宽字符function containsZeroWidthCharacter(str) {// 正则表达式匹配零宽字符const zeroWidthRegex = /[\u200B\u200C\u200D\uFEFF]/g;return zeroWidthRegex.test(str);}console.log(containsZeroWidthCharacter("Hello\u200B World")); // trueconsole.log(containsZeroWidthCharacter("Hello World")); // false</script>

常见的零宽字符包括:

1、零宽空格【Unicode:U+200B】
这是一个没有宽度的空格,可以在两个字符之间插入,但不会在视觉上产生间隔。
2、零宽非连接符【Unicode:U+200C】
用于阻止字符连接。在阿拉伯语或印地语中,用来控制哪些字符应该连接,哪些不应连接。
3、零宽连接符【Unicode:U+200D】
用于强制某些字符连接在一起,这通常在一些复合字符或表情符号中起作用。与2相反
4、零宽非换行空格【Unicode:U+FEFF】
用于文件中的字节顺序标记,但也可以用作零宽空格的一种形式。作用是防止换行。

尽管零宽字符对用户不可见,但它们会占用存储空间,通常用于文本隐写防止链接自动化处理格式化和排版。比如文本处理时加上零宽字符,可以防止文本被盗窃,解码后是自己的名称。

三、目录滚动时自动高亮

    <style>body {display: flex;margin: 0;}/* 左侧目录 */.sidebar {width: 250px;background-color: #333;color: white;padding: 20px;height: 100vh;position: fixed;top: 0;left: 0;overflow-y: auto;}.sidebar a {color: white;text-decoration: none;display: block;padding: 10px;margin: 5px 0;}.sidebar a:hover {background-color: #575757;}.highlight {background-color: #ffcc00; /* 高亮颜色 */}/* 右侧内容 */.content {margin-left: 280px; /* 留出左侧目录栏空间 */padding: 20px;width: calc(100% - 260px);}h1 {color: #333;}.section {margin-bottom: 30px;height: 750px;border: 1px solid yellowgreen;}.section h2 {color: #444;}</style><body><div class="sidebar toc"><h2>目录</h2><a href="#section1">部分 1</a><a href="#section2">部分 2</a><a href="#section3">部分 3</a><a href="#section4">部分 4</a></div><div class="content"><div class="section" id="section1"><h2>部分 1</h2><p>第一部分内容。</p></div><div class="section" id="section2"><h2>部分 2</h2><p>第二部分内容。</p></div><div class="section" id="section3"><h2>部分 3</h2><p>第三部分内容。</p></div><div class="section" id="section4"><h2>部分 4</h2><p>第四部分内容。</p></div></div><script>function highlight(id) {document.querySelectorAll("a.highlight").forEach((a) => a.classList.remove("highlight"));if (id instanceof HTMLElement) {id.classList.add("highlight");return;}if (id.startsWith("#")) {id = id.substring(1);}document.querySelector(`a[href="#${id}"]`).classList.add("highlight");}const links = document.querySelectorAll('.toc a[href^="#"]');const titles = [];for (const link of links) {link.addEventListener("click", () => {highlight(link.getAttribute("href").substring(1));});const url = new URL(link.href);const dom = document.querySelector(url.hash);if (dom) {titles.push(dom);}}function debounce(fn, delay = 100) {let timer = null;return function (...args) {clearTimeout(timer);timer = setTimeout(() => {fn.apply(this, args);}, delay);};}const scrollHandler = debounce(() => {const rects = titles.map((title) => title.getBoundingClientRect());const range = 300;for (let i = 0; i < titles.length; i++) {const title = titles[i];const rect = rects[i];// 标题区域在指定范围内就高亮if (rect.top >= 0 && rect.top <= range) {highlight(title.id);break;}// 当前内容标题在展示视口之上,并且下一个标题在展示视口之下,此时高亮此标题if (rect.top < 0 &&rects[i + 1] &&rects[i + 1].top > document.documentElement.clientHeight) {highlight(title.id);break;}}}, 100);window.addEventListener("scroll", scrollHandler);</script></body>

四、高亮关键字

<style>.highlight {color: red;font-weight: bold;}
</style><body><div><input type="text" class="txtKeyword" /><ul></ul></div><script>const ul = document.querySelector("ul");const txtKeyword = document.querySelector(".txtKeyword");function setHTML(lists) {ul.innerHTML = lists.map((l) => {let cname = l.cname;// 如果输入框有内容,则进行高亮匹配if (txtKeyword.value) {const reg = new RegExp(txtKeyword.value, "ig");cname = cname.replace(reg, function (key) {return `<span class="highlight">${key}</span>`;});}return `<li><span>${cname}</span></li>`;}).join(""); // 使用 join 合并生成的 HTML 字符串}const NameLists = [{ cname: "前端" },{ cname: "后端" },{ cname: "测试员" },{ cname: "运维师" },];// 筛选包含关键字的元素function filterList() {const keyword = txtKeyword.value.trim();if (keyword) {const filtered = NameLists.filter((item) =>item.cname.match(new RegExp(keyword, "i")));setHTML(filtered);} else {setHTML(NameLists);}}setHTML(NameLists);// 给输入框添加监听事件,以便动态更新txtKeyword.addEventListener("input", filterList);</script></body>

五、文字描边

第一种text-shadow:给8个方向即可,但是连接处有瑕疵(见红色边缘部分)

@mixin text-stroke($color: #fff, $width: 1px) {text-shadow: 0 -#{$width} #{$color}, #{$width} 0 #{$color},0 #{$width} #{$color}, -#{$width} 0 #{$color}, #{$width} #{$width} #{$color},-#{$width} -#{$width} #{$color}, #{$width} -#{$width} #{$color},-#{$width} #{$width} #{$color};
}
p {font-size: 50px;font-weight: bold;@include text-stroke(red, 2px);// color: transparent; 不支持文字透明
}
p {font-size: 50px;font-weight: bold;text-shadow: 0 -2px gold, 2px 0 gold, 0 2px gold, -2px 0 gold,/* 上、右、下、左 */ 2px 2px gold, -2px -2px gold, 2px -2px gold,-2px 2px gold; /* 四个对角线 */
}

第二种-webkit-text-stroke不仅边缘平滑,并且支持透明

p {font-size: 50px;font-weight: bold;-webkit-text-stroke: 2px red;color: transparent; //支持文字透明position: relative;
}
// -webkit-text-stroke是居中描边,原来字体变小了,可以在"画一层"盖上去
p::after {content: attr(data-text);position: absolute;left: 0;top: 0;-webkit-text-stroke: 0;
}

六、按钮边框的旋转动画

原理:在按钮层级下加一个矩形,围绕按钮中心进行360度旋转,多余矩形隐藏

    <style>button {width: 100px;height: 50px;color: white;outline: none;z-index: 1;border-radius: 10px;cursor: pointer;background: black;/* outline: 4px solid gold; */position: relative;overflow: hidden;}button::before {content: "";position: absolute;width: 200%;height: 200%;background: blue;z-index: -2;left: 50%;top: 50%;transform-origin: left top;/* 圆点在左上角 */animation: rotation 2s linear infinite;}button::after {content: "";position: absolute;--g: 4px;width: calc(100% - var(--g) * 2);height: calc(100% - var(--g) * 2);background: black;left: var(--g);top: var(--g);border-radius: inherit;z-index: -1;}@keyframes rotation {to {transform: rotate(360deg);}}</style>

七、视频文字特效

  <style>.txt{position:absolute;inset: 0;background: #fff;display: flex;justify-content: center;align-items: center;mix-blend-mode: screen; /* 增强亮度,使图像或元素显得更加明亮 */}</style>
<body><div class="container"><video src="./fire.mp4" autoplay muted></video><div class="txt">大前端</div></div>
</body>

八、立体文字特效+让文字立起来

放大看是叠加出来的,真正要做得建模

    <style>body {background-color: brown;color: #fff;padding: 30px;}.text1 {font-size: 5em;text-shadow: -1px 1px #bbb, -2px 2px #bbb, -3px 3px #bbb, -4px 4px #bbb,-5px 5px #bbb, -10px 10px 3px #0008;}.text2 {font-weight: 700;position: relative;}.text2::after {content: "DARKNESS";position: absolute;left: 0;top: 0;color: #000;transform: translate(-25px, 2px) scale(0.9) skew(50deg);z-index:-1;filter: blur(2px);mask:linear-gradient(transparent,#000)}</style><body><h1 class="text1">立体文字</h1><h1 class="text2">DARKNESS</h1></body>

九、文字连续光影特效

span {color: #faebd7;animation: colorChange 1s infinite alternate;
}
@keyframes colorChange {to {color: #ff0266;}
}
@for $i from 1 through 7 {span:nth-child(#{$i}) {animation-delay: ($i - 1) * 0.1s;}
}

十、重复渐变的边框

    <style>.card {width: 217px;margin: 0 auto;color: #333;line-height: 1.8;border-radius: 10px;background: repeating-linear-gradient(-45deg,#e8544d 0 10px,#fff 10px 20px,#75adf8 20px 30px,#fff 30px 40px) -20px -20px/200% 200%;padding: 5px;transition: 0.5s;}.card:hover {background-position: 0 0;}.container {background: #fff;border-radius: inherit;}</style><body><div class="card"><div class="container">重复渐变的边框原理:<br/>设置背景为重复的线性渐变。渐变角度为-45度,包含四个颜色区块。</div></div></body>

十一、磨砂玻璃效果

    <style>.wrap {text-align: center;color: white;}.modal {background: rgba(255, 255, 255, 0.4); /* 半透明背景 */backdrop-filter: blur(10px); /* 模糊背景 */border-radius: 15px; /* 圆角 */padding: 40px;width: 300px;box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); /* 增加阴影 */font-size: 24px;}</style><body><div class="wrap"><div class="modal">磨砂玻璃效果</div></div></body>

十二、各种FLIP动画

参考:https://zhuanlan.zhihu.com/p/712766286

    <style>.box {width: 60px;height: 60px;background-color: skyblue;color: white;font-size: 30px;margin: 10px;}</style><body><div class="container" style="display: flex"><div class="box" key="1">1</div><div class="box" key="2">2</div><div class="box" key="3">3</div><div class="box" key="4">4</div><div class="box" key="5">5</div></div><button onclick="shuffle()">打乱</button><script>function shuffle() {const container = document.querySelector(".container");const boxes = Array.from(container.children);// First: 记录每个盒子的起始位置const startPositions = boxes.reduce((result, box) => ({...result,[box.getAttribute("key")]: box.getBoundingClientRect(),}),{});// 随机打乱盒子顺序,然后把打乱好的盒子放回 DOMboxes.sort(() => Math.random() - 0.5);boxes.forEach((box) => container.appendChild(box));// Last: 记录每个盒子的最终位置const endPositions = boxes.reduce((result, box) => ({...result,[box.getAttribute("key")]: box.getBoundingClientRect(),}),{});// Invert: 计算 “反向” 偏移量boxes.forEach((box) => {const key = box.getAttribute("key");const start = startPositions[key];const end = endPositions[key];// 注意,此时 DOM 已经处于最终位置,所以它的 translate 是 “反向” 的// 所以要用 first 来减去 lastconst deltaX = start.left - end.left;const deltaY = start.top - end.top;// 如果元素 “原地不动”,那么跳过后续流程if (deltaX === 0 && deltaY === 0) {return;}// 让元素通过 transform 偏移回到起点box.style.transition = null; // 暂时屏蔽掉过渡,实际生产此处需完善box.style.transform = `translate(${deltaX}px, ${deltaY}px)`;// Play: 在重绘之前,撤掉 transform 偏移,播放 “归位” 过渡动画requestAnimationFrame(() => {box.style.transition = `transform 2s`;box.style.transform = "";});// FLIP 动画完成后,清理残余样式box.addEventListener("transitionend",() => {box.style.transition = null;box.style.transform = null;},{ once: true });});}</script></body>

其中的“Invert” 和 “Play” 步骤可以使用 Web Animation API 进行简化

    <style>.box {width: 60px;height: 60px;color: white;font-size: 30px;margin: 10px;box-sizing: border-box;background-color: skyblue;border: 2px black solid;transition: width 500ms, height 500ms;}.scale {position: absolute;top: 90px;left: 10px;width: 120px;height: 120px;z-index: 10;}</style><body><div class="container" style="display: flex"><div class="box" key="1">1</div><div class="box" key="2">2</div><div class="box" key="3">3</div><div class="box" key="4">4</div><div class="box" key="5">5</div></div><script>const container = document.querySelector('.container')const boxes = Array.from(container.children)boxes.forEach(box => {box.addEventListener('click', () => {// First: 记录每个盒子的起始位置const startPositions = boxes.reduce((result, box) => ({...result,[box.getAttribute('key')]: box.getBoundingClientRect(),}),{})box.classList.toggle('scale')// Last: 记录每个盒子的最终位置const endPositions = boxes.reduce((result, box) => ({...result,[box.getAttribute('key')]: box.getBoundingClientRect(),}),{})// Invert: 计算 “反向” 偏移量boxes.forEach(box => {const key = box.getAttribute('key')const start = startPositions[key]const end = endPositions[key]// 注意,此时 DOM 已经处于最终位置,所以它的 transform 是 “反向” 的// 所以要用 first 来减去 lastconst deltaX = start.left - end.leftconst deltaY = start.top - end.top// 如果元素 “原地不动”,那么跳过后续流程if (deltaX === 0 && deltaY === 0) {return}// 将盒子通过 transform 移至初始位置box.style.transition = ''box.style.transform = `translate(${deltaX}px, ${deltaY}px)`// Play: 播放动画应用变换requestAnimationFrame(() => {box.style.transition = `all 500ms`box.style.transform = ''})// FLIP 动画完成后,清理残余样式box.addEventListener('transitionend',() => {box.style.transition = nullbox.style.transform = null},{ once: true })})})})</script></body>

Vue 内置组件 <TransitionGroup> 已经实现了 FLIP 动画

<template><TransitionGroup style="display: flex" name="flip" tag="div"><div class="box" v-for="item of list" :key="item">{{ item }}</div></TransitionGroup><button @click="shuffle">打乱</button>
</template>
<script setup>
import { reactive } from 'vue'
const list = reactive([1, 2, 3, 4, 5])
const shuffle = () => void list.sort(() => Math.random() - 0.5)
</script>
<style scoped>
.box {width: 60px;height: 60px;background-color: skyblue;color: white;font-size: 30px;margin: 10px;
}
.flip-move {transition: all 2s;
}
</style>

react-flip-toolkit 工具是一个用于实现组件 FLIP 动画的 React 库。

http://www.lryc.cn/news/519244.html

相关文章:

  • ceph 数据均衡
  • 代码随想录算法训练营day29
  • android studio根据包名获取当前安装包信息
  • 学习第六十五行
  • 零碎的知识点(七):线性二次调节器(LQR)是什么?
  • Matlab一些使用技巧
  • Linux 发行版介绍与对比:Red Hat、Ubuntu、Kylin、Debian
  • 从CentOS到龙蜥:企业级Linux迁移实践记录(龙蜥开局)
  • java1-相对路径与绝对路径
  • iChainfo 品牌升級為 ichaingo,打造 Web3 數據基礎設施新標杆
  • Flink概念知识讲解之:Restart重启策略配置
  • [java基础-集合篇]LinkedList源码粗析
  • 面试:C++类成员初始化顺序
  • 【Python】Python与C的区别
  • [开源]自动化定位建图系统(视频)
  • ISP流程--去马赛克详解
  • Objective-C语言的软件工程
  • Objective-C语言的语法糖
  • 设计模式中的代理模式
  • 15个学习Python 的编程游戏网站
  • 微信小程序实现拖拽盒子效果
  • Linux-蓝牙协议
  • moviepy 将mp4视频文件提取音频mp3 - python 实现
  • imageio 图片转mp4 保存mp4
  • Postman接口测试04|批量运行测试用例、参数化、Mock Server、Cookie鉴权、Newman生成测试报告
  • 学技术学英语:http状态码 401 Unauthorized vs 403 Forbidden
  • @LocalBuilder装饰器: 维持组件父子关系
  • React(二)——Admin主页/Orders页面/Category页面
  • 移动端屏幕分辨率rem,less
  • Docker Desktop 构建java8基础镜像jdk安装配置失效解决