荧光笔标记

句子进入视野时,一道荧光笔从关键词后方划过,把视线引到重点上。

  • CSS
  • 文字
  • 进场
  • 收录于

好的动效, 先被感受到,才被看见。

AI 提示词

制作一个在进入视口时触发的荧光笔划过效果。把关键短语包在 <mark> 中,背景色设为透明,background-image 为 linear-gradient(45% 不透明度的主色),尺寸为 0% 40%,位置在 left 85%,background-repeat: no-repeat,并使用 box-decoration-break: clone 让它在换行时也能正常显示。当 IntersectionObserver 检测到句子可见 50% 时,设置一个 data 属性,在 200ms 延迟后用 800ms、cubic-bezier(0.16, 1, 0.3, 1) 把 background-size 动到 100% 40%。添加一个“重播”按钮,移除再重新加上该属性。在 prefers-reduced-motion 下直接显示已填满的高亮。句子为“好的动效,先被感受到,才被看见。”,其中“先被感受到,才被看见”被高亮。

源代码

Demo.tsx
'use client'

import { useEffect, useRef } from 'react'
import s from './demo.module.css'

export default function HighlighterMark({ labels }: { labels: Record<string, string> }) {
  const ref = useRef<HTMLParagraphElement>(null)

  useEffect(() => {
    const el = ref.current
    if (!el) return
    const io = new IntersectionObserver(
      ([e]) => {
        if (!e.isIntersecting) return
        el.setAttribute('data-on', '')
        io.disconnect()
      },
      { threshold: 0.5 },
    )
    io.observe(el)
    return () => io.disconnect()
  }, [])

  const replay = () => {
    const el = ref.current
    if (!el) return
    el.removeAttribute('data-on')
    void el.offsetWidth
    el.setAttribute('data-on', '')
  }

  return (
    <div className={s.wrap}>
      <p ref={ref} className={s.text}>
        {labels.before} <mark className={s.mark}>{labels.mark}</mark>
        {labels.after}
      </p>
      <button type="button" className={s.replay} onClick={replay}>
        {labels.replay}
      </button>
    </div>
  )
}
demo.module.css
.wrap {
  display: grid;
  justify-items: center;
  gap: 16px;
  max-width: 300px;
}

.text {
  margin: 0;
  font: 600 var(--text-xl) / 1.4 var(--font-display);
  text-align: center;
  color: var(--stage-ink);
}

.mark {
  background-color: transparent;
  background-image: linear-gradient(color-mix(in oklch, var(--stage-accent) 45%, transparent), color-mix(in oklch, var(--stage-accent) 45%, transparent));
  background-repeat: no-repeat;
  background-position: left 85%;
  background-size: 0% 40%;
  color: inherit;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

.text[data-on] .mark {
  animation: sweep 800ms var(--ease-out) 200ms forwards;
}

@keyframes sweep {
  to { background-size: 100% 40%; }
}

.replay {
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--stage-rule);
  border-radius: var(--radius-sm);
  background: var(--stage-surface);
  color: var(--stage-ink);
  font: 500 var(--text-sm) / 1 var(--font-body);
  white-space: nowrap;
  cursor: pointer;
}

.replay:hover { border-color: var(--stage-accent); }
.replay:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .mark { background-size: 100% 40%; }
  .text[data-on] .mark { animation: none; }
}

投稿创意

看到想收录的动效或主题?简单描述一下,会打开一个预先填好的 GitHub Issue,确认后即可提交。

你想投稿的是?

需要 GitHub 账号,投稿内容会公开。