html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文本变形动画</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- 两个文本部分 -->
<div id="container">
<span id="text1"></span>
<span id="text2"></span>
</div>
<!-- 用于创建合并效果的 svg 过滤器 -->
<svg id="filter">
<defs>
<filter id="threshold">
<!-- 这里只是一个阈值效果--像素足够高的地方不透明度设置为完全不透明,其他地方像素设置为完全透明。 -->
<feColorMatrix in="SourceGraphic"
type="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 255 -140" />
</filter>
</defs>
</svg>
<!-- partial -->
<script src="./script.js"></script>
</body>
</html>
css
/* 引用文字 */
@import url('https://fonts.googleapis.com/css?family=Raleway:900&display=swap');
body {
margin: 0px;
}
#container {
/* 将文本在视口里居中 */
position: absolute;
margin: auto;
width: 100vw;
height: 80pt;
top: 0;
bottom: 0;
/* 这个过滤器很神奇哦~可以试着注释一下,看变形是如何工作的~ */
filter: url(#threshold) blur(0.6px);
}
#text1, #text2 {
position: absolute;
width: 100%;
display: inline-block;
font-family: 'Raleway', sans-serif;
font-size: 80pt;
text-align: center;
user-select: none;
}
js
/*
巧妙地利用SVG过滤器来创建"变形文本"效果。
从本质上讲,它将两个文本元素层叠在一起,并根据哪个文本应该更突出来模糊它们。
一旦应用了模糊,两个文本一起通过一个阈值过滤器,产生"胶粘"效果。
*/
const elts = {
text1: document.getElementById("text1"),
text2: document.getElementById("text2") };
/* 要在其中变形的字符串。你可以把这些换成你想要的任何东西! */
const texts = [
"Can",
"I",
"have",
"one",
"click",
"triple?"
];
/* 控制变形的速度。 */
const morphTime = 1;
const cooldownTime = 0.25;
let textIndex = texts.length - 1;
let time = new Date();
let morph = 0;
let cooldown = cooldownTime;
elts.text1.textContent = texts[textIndex % texts.length];
elts.text2.textContent = texts[(textIndex + 1) % texts.length];
function doMorph() {
morph -= cooldown;
cooldown = 0;
let fraction = morph / morphTime;
if (fraction > 1) {
cooldown = cooldownTime;
fraction = 1;
}
setMorph(fraction);
}
/* 将模糊过滤器应用于文本 */
function setMorph(fraction) {
// fraction = Math.cos(fraction * Math.PI) / -2 + .5;
elts.text2.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;
elts.text2.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;
fraction = 1 - fraction;
elts.text1.style.filter = `blur(${Math.min(8 / fraction - 8, 100)}px)`;
elts.text1.style.opacity = `${Math.pow(fraction, 0.4) * 100}%`;
elts.text1.textContent = texts[textIndex % texts.length];
elts.text2.textContent = texts[(textIndex + 1) % texts.length];
}
function doCooldown() {
morph = 0;
elts.text2.style.filter = "";
elts.text2.style.opacity = "100%";
elts.text1.style.filter = "";
elts.text1.style.opacity = "0%";
}
/* 动画循环 */
function animate() {
requestAnimationFrame(animate);
let newTime = new Date();
let shouldIncrementIndex = cooldown > 0;
let dt = (newTime - time) / 1000;
time = newTime;
cooldown -= dt;
if (cooldown <= 0) {
if (shouldIncrementIndex) {
textIndex++;
}
doMorph();
} else {
doCooldown();
}
}
/* 启动动画。 */
animate();