构建拆分文本动画

简要介绍如何构建拆分字母和字词动画。

在这篇博文中,我想分享一些想法,让您了解如何为 Web 解决拆分文本动画和互动问题,使其尽可能减少跨浏览器访问和实现网页互动。试用演示

演示

如果你更喜欢视频,可以参考本博文的 YouTube 版本:

概览

拆分文本动画非常棒。在这篇博文中,我们几乎不会触及动画的潜力,但它确实为构建动画提供了基础。目标是渐进地添加动画效果。默认情况下,文本应清晰可辨,并且动画在顶部构建。拆分文本的动作效果可能过于杂乱,并可能会造成干扰,因此我们只操纵 HTML,或在用户能够接受动作时应用动作样式。

下面简要介绍了相关工作流程和取得的成效:

  1. 准备:为 CSS 和 JS 减少动作条件变量。
  2. JavaScript 中的 Prepare 拆分文本实用程序。
  3. 在网页加载时对条件和实用程序进行编排
  4. 为字母和单词(Rad 部分!)编写 CSS 过渡和动画。

下面是我们将要得到的条件结果的预览:

Chrome 开发者工具的屏幕截图,其中“元素”面板处于打开状态,“减少”动作被设置为“减少”,并且 h1 未拆分
用户更喜欢简短动作:文字清晰可辨 / 未拆分

如果用户更喜欢快动作,我们会单独保留 HTML 文档,不添加动画。如果动作可以正常,我们就继续将其分成几块。下面是 JavaScript 按字母拆分文本后的 HTML 预览。

Chrome 开发者工具的屏幕截图,其中“元素”面板处于打开状态,“减少”动作被设置为“减少”,并且 h1 未拆分
用户符合动作;文本拆分为多个 <span> 元素

准备动作条件

此项目中的 CSS 和 JavaScript 将使用便捷的 @media (prefers-reduced-motion: reduce) 媒体查询。此媒体查询是我们决定是否拆分文本的主要条件。CSS 媒体查询将用于保留转场和动画,而 JavaScript 媒体查询将用于保留 HTML 操作。

准备 CSS 条件

我使用 PostCSS 启用了媒体查询级别 5 的语法,在这种语法中,我可以将媒体查询布尔值存储到变量中:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

准备 JS 条件

在 JavaScript 中,浏览器提供了一种检查媒体查询的方法,我使用了解构功能,从媒体查询检查中提取并重命名布尔值结果:

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

然后,我可以测试 motionOK,并且仅在用户未要求减少动作时更改文档。

if (motionOK) {
  // document split manipulations
}

我可以使用 PostCSS 启用嵌套草稿 1 中的 @nest 语法来检查相同的值。这样一来,我可以将有关动画的所有逻辑及其对父级和子级的样式要求存储在一个位置:

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

借助 PostCSS 自定义属性和 JavaScript 布尔值,我们可以有条件地升级效果。这就进入下一部分,我将详细介绍用于将字符串转换为元素的 JavaScript。

拆分文本

无法使用 CSS 或 JS 单独为文字字母、字词、行等添加动画效果。为了实现这个效果,我们需要方框。如果要为每个字母添加动画效果 那么每个字母都必须是一个元素如果要为每个单词添加动画效果,那么每个单词都需要是一个元素。

  1. 创建用于将字符串拆分为元素的 JavaScript 实用函数
  2. 编排这些实用程序的使用

拆分字母实用函数

一个有趣的入手点就是一个函数,该函数接受一个字符串并返回数组中的每个字母。

export const byLetter = text =>
  [...text].map(span)

ES6 中的 spread 语法确实有助于这项任务迅速完成。

拆分单词实用函数

与拆分字母类似,此函数接受一个字符串并返回数组中的每个单词。

export const byWord = text =>
  text.split(' ').map(span)

通过 JavaScript 字符串的 split() 方法,我们可以指定要截断哪些字符。我传递了一个空白区域,表示字词已拆分。

将方框设为实用函数

效果需要为每个字母绘制方框,我们可以看到,在这些函数中,使用 span() 函数调用 map()。以下是 span() 函数。

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

请务必注意,设置了数组位置时名为 --index 的自定义属性。为字母动画添加框固然不错,但在 CSS 中使用索引似乎是一个小小的添加,但影响非常大。最明显的是,这一巨大影响是令人惊讶的。我们将可以使用 --index 作为动画偏移的方式,以实现交错外观。

公共事业公司总结

即将完成的 splitting.js 模块:

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

export const byLetter = text =>
  [...text].map(span)

export const byWord = text =>
  text.split(' ').map(span)

接下来是导入并使用这些 byLetter()byWord() 函数。

拆分编排

有了可用的拆分实用程序,将其整合在一起意味着:

  1. 查找要拆分的元素
  2. 将其拆分替换到 HTML 中的文本

之后,CSS 即会接管,并为元素 / 框添加动画效果。

查找元素

我选择使用属性和值来存储有关所需动画以及如何拆分文本的信息。我喜欢将这些声明式选项 放入 HTML 文件中在 JavaScript 中,属性 split-by 用于查找元素并为字母或字词创建方框。在 CSS 中使用 letter-animationword-animation 属性来定位元素子元素以及应用转换和动画。

以下 HTML 示例演示了这两个属性:

<h1 split-by="letter" letter-animation="breath">animated letters</h1>
<h1 split-by="word" word-animation="trampoline">hover the words</h1>

从 JavaScript 中查找元素

我使用 CSS 选择器语法来表示属性存在状态,以收集需要文本拆分的元素的列表:

const splitTargets = document.querySelectorAll('[split-by]')

从 CSS 中查找元素

我还使用了 CSS 中的属性存在状态选择器,为所有字母动画提供相同的基本样式。稍后,我们将使用此属性值添加更具体的样式,以实现某种效果。

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

正在原位拆分文本

对于在 JavaScript 中找到的每个分屏目标,我们将根据属性的值拆分其文本,并将每个字符串映射到 <span>。然后,我们可以将元素的文本替换为我们制作的方框:

splitTargets.forEach(node => {
  const type = node.getAttribute('split-by')
  let nodes = null

  if (type === 'letter') {
    nodes = byLetter(node.innerText)
  }
  else if (type === 'word') {
    nodes = byWord(node.innerText)
  }

  if (nodes) {
    node.firstChild.replaceWith(...nodes)
  }
})

编排总结

已完成 index.js

import {byLetter, byWord} from './splitting.js'

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

if (motionOK) {
  const splitTargets = document.querySelectorAll('[split-by]')

  splitTargets.forEach(node => {
    const type = node.getAttribute('split-by')
    let nodes = null

    if (type === 'letter')
      nodes = byLetter(node.innerText)
    else if (type === 'word')
      nodes = byWord(node.innerText)

    if (nodes)
      node.firstChild.replaceWith(...nodes)
  })
}

JavaScript 可以用以下英语阅读:

  1. 导入一些辅助实用函数。
  2. 检查此用户的动作是否合适,如果不合适,什么都不做。
  3. 针对要拆分的每个元素。
    1. 请根据它们的拆分方式进行拆分。
    2. 将文字替换为元素。

拆分动画和转场效果

上述拆分文档操作利用 CSS 或 JavaScript 解锁了大量潜在的动画和效果。本文底部提供了一些链接,可帮助您激发拆分潜力。

是时候展示它能做什么了!我将分享 4 个 CSS 驱动的动画和过渡效果。🤓

拆分字母

作为拆分字母效果的基础,我发现以下 CSS 很有帮助。我将所有转场效果和动画置于动态媒体查询后面,然后为每个新的子字母 span 提供一个显示属性,以及一个用于说明如何处理空格的样式:

[letter-animation] > span {
  display: inline-block;
  white-space: break-spaces;
}

空格样式很重要,这样,仅是空间的 span 不会被布局引擎收起。现在,我们来看看有状态的有趣内容。

过渡字母切换示例

此示例使用了 CSS 转换来实现拆分文本效果。对于过渡,我们需要让引擎在不同状态之间添加动画效果,我选择了三种状态:不悬停、在句子中悬停、悬停在字母上。

当用户将鼠标悬停在句子(也称为容器)上时,系统会缩容所有子项,就像用户将其推开一样。然后,当用户将光标悬停在某个字母上时 我将其移到上一项

@media (--motionOK) {
  [letter-animation="hover"] {
    &:hover > span {
      transform: scale(.75);
    }

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:hover {
        transform: scale(1.25);
      }
    }
  }
}

为拆分字母添加动画效果示例

此示例使用预定义的 @keyframe 动画对每个字母无限添加动画效果,并利用内嵌自定义属性索引创建交错效果。

@media (--motionOK) {
  [letter-animation="breath"] > span {
    animation:
      breath 1200ms ease
      calc(var(--index) * 100 * 1ms)
      infinite alternate;
  }
}

@keyframes breath {
  from {
    animation-timing-function: ease-out;
  }
  to {
    transform: translateY(-5px) scale(1.25);
    text-shadow: 0 0 25px var(--glow-color);
    animation-timing-function: ease-in-out;
  }
}

拆分字词

在这些示例中,Flexbox 是我这里的容器类型,很好地利用 ch 单元作为合理的间隔长度。

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
显示字词间间隔的 Flexbox 开发者工具

转换拆分字词示例

在这个过渡示例中,我再次使用了悬停功能。由于效果最初会隐藏内容直至悬停,因此我确保仅在设备能够悬停时才会应用交互和样式。

@media (hover) {
  [word-animation="hover"] {
    overflow: hidden;
    overflow: clip;

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:not(:hover) {
        transform: translateY(50%);
      }
    }
  }
}

为拆分字词添加动画效果示例

在这个动画示例中,我再次使用 CSS @keyframes,在常规文本段落上创建交错的无限动画。

[word-animation="trampoline"] > span {
  display: inline-block;
  transform: translateY(100%);
  animation:
    trampoline 3s ease
    calc(var(--index) * 150 * 1ms)
    infinite alternate;
}

@keyframes trampoline {
  0% {
    transform: translateY(100%);
    animation-timing-function: ease-out;
  }
  50% {
    transform: translateY(0);
    animation-timing-function: ease-in;
  }
}

总结

现在你已经知道我是怎么做的,你知道该怎么做呢?!🙂

下面,我们就来介绍一下我们的方法多样化,并了解在 Web 上构建网站的所有方法。 创建一个 Codepen 或托管自己的演示,在 Twitter 上向我提出,我会将其添加到下面的“社区混剪”部分。

来源

更多演示和灵感

社区混剪作品