익스클루시브

Wind Text <mv-wind-text>

페이지 전체가 공유하는 하나의 바람장 안에서 글자들이 풀(또는 깃발)처럼 움직이는 텍스트입니다. 스크롤하면 돌풍이 일어 그 전선이 스크롤 방향으로 텍스트를 쓸고 지나가고, 커서는 소멸하는 이류 속도 그리드 위에서 국소적으로 바람을 불며, 각 글자는 자체 스프링으로 기준선을 중심으로 휘고, 바람이 부는 곳마다 꽃가루 몇 알이 날아오릅니다.

카테고리애니메이션 텍스트
유형Web Component (<mv-wind-text>)
상태안정
Keywordsexclusive, wind, text, typography, scroll, cursor, spring, fluid, particles, pollen, editorial, headline

When to use

  • An editorial headline should sway like grass or a flag in response to scroll and cursor
  • An organic or nature brand wants a gentle ambient breeze with drifting pollen

Avoid when

  • The text contains links or buttons; they are flattened to plain text
  • Long paragraphs at full strength; swaying letters hurt reading, so keep strength low or skip it

설치

node scripts/add.mjs wind-text --out ./src/marvelous

Marvelous UI MCP 서버를 사용하는 AI 에이전트: install_components({ slugs: ["wind-text"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

복사되는 파일(의존성 포함): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/wind-text/wind-text.js, components/wind-text/wind-text.css.

사용법

기본 마크업입니다. 여기서 시작해 속성, data-*, CSS 변수로 커스터마이즈하세요:

<section id="wind-demo" class="wt-hero" lang="en">
  <style>
    .wt-hero { position: relative; overflow: hidden; width: min(100%, 64rem); margin-inline: auto; padding: clamp(2rem, 1rem + 5vw, 4.5rem) clamp(1.25rem, 1rem + 4vw, 4rem) 3rem; border-radius: var(--mv-radius-2xl); border: 1px solid var(--mv-border);
      background:
        radial-gradient(120% 70% at 85% 110%, color-mix(in oklch, var(--mv-success) 16%, transparent), transparent 60%),
        radial-gradient(90% 60% at 0% 0%, color-mix(in oklch, var(--mv-warning) 12%, transparent), transparent 65%),
        var(--mv-surface);
      font-family: var(--mv-font-sans); }
    .wt-kicker { margin: 0 0 1.25rem; display: flex; gap: .625rem; align-items: center; font-size: var(--mv-text-xs); letter-spacing: .14em; text-transform: uppercase; color: var(--mv-fg-muted); font-weight: var(--mv-weight-semibold); }
    .wt-kicker::before { content: ""; width: 1.75rem; height: 1px; background: currentColor; opacity: .6; }
    .wt-title { margin: 0; max-width: 15ch; font-size: clamp(2.75rem, 1.5rem + 5.2vw, 5.25rem); line-height: 1.02; letter-spacing: -0.035em; font-weight: 800; color: var(--mv-fg); text-wrap: balance; }
    .wt-title em { font-style: normal; color: color-mix(in oklch, var(--mv-success) 78%, var(--mv-fg)); }
    .wt-lede { margin: 1.5rem 0 0; max-width: 40rem; font-size: var(--mv-text-lg); line-height: 1.6; color: var(--mv-fg-muted); }
    .wt-actions { margin-top: 2rem; display: flex; gap: .75rem; flex-wrap: wrap; align-items: center; }
    .wt-hint { margin-left: auto; font-size: var(--mv-text-xs); color: var(--mv-fg-subtle); }
    .wt-meadow { position: absolute; inset: auto 0 0 0; height: 5rem; pointer-events: none; opacity: .5;
      background: repeating-linear-gradient(90deg, color-mix(in oklch, var(--mv-success) 30%, transparent) 0 1px, transparent 1px 9px);
      mask-image: linear-gradient(to top, #000, transparent); }
  </style>
  <p class="wt-kicker">Highland Seed Co. · Spring 2026 catalog</p>
  <h2 class="wt-title"><mv-wind-text strength="1.25" pollen="26">Sow to every <em>wind.</em></mv-wind-text></h2>
  <p class="wt-lede"><mv-wind-text strength="0.55" pollen="8">Open-pollinated heirloom seeds, grown without chemicals in the Vermont hills. Every packet saves an old variety from oblivion: Brandywine tomato, scarlet runner bean, bearded Red Fife wheat.</mv-wind-text></p>
  <div class="wt-actions">
    <a class="mv-button" href="#wind-demo">Browse the catalog</a>
    <button class="mv-button" data-variant="outline" type="button" data-gust>Make it blow</button>
    <span class="wt-hint">Move your mouse or scroll</span>
  </div>
  <div class="wt-meadow" aria-hidden="true"></div>
</section>
<script type="module">
  const root = document.getElementById("wind-demo");
  const { gust } = await customElements.whenDefined("mv-wind-text");
  let dir = 1;
  root.querySelector("[data-gust]").addEventListener("click", () => { gust(1.3, dir); dir = -dir; });
  // A first gust on arrival so the idea reads instantly.
  setTimeout(() => gust(1.1, 1), 350);
</script>

API

Attributes

Name유형DefaultDescription
variantgrass | flaggrassgrass: each letter bends and sways around its base; flag: a wave runs along the line like a pennant.
strengthnumber1Letter flexibility (0 to 3): 0.5 for a paragraph, 1.2 for a big headline.
pollennumber18Maximum number of pollen/dust grains in flight (0 = none).
breezenumber0Constant ambient breeze (0 to 1). Above 0, the animation runs as long as the text is visible.

Methods

NameDescription
gust(strength = 1, direction = 1)Sends a gust across every instance (direction 1 → right, −1 → left). Also available as a static: MvWindText.gust().

Content structure

NameDescription
(text)Plain text; inline tags (em, strong, span) are kept visually, links and buttons are flattened to text.

CSS classes

NameDescription
mv-wind-text-word / -charGenerated words (non-breaking) and letters; letters pivot near the baseline (transform-origin 50% 84%).
mv-wind-text-canvasPollen canvas, overflowing by 64 px.

CSS variables

NameDefaultDescription
--mv-wind-text-pollenvar(--mv-warning)Pollen grain color.

Accessibility

The text is exposed only once, through a visually hidden copy; the animated letters and the canvas are aria-hidden, purely decorative, with no interaction required. One wind field, one loop and passive listeners for the whole page; the loop stops as soon as the air is calm and the letters are upright, and only computes visible instances. On touch devices, only scrolling blows. Reduced motion: no animation, letters stay still and perfectly readable. Forced colors: the pollen canvas is hidden. Known limits: the text is read once, on first connection (to change it, render a new element, for example with a key in your framework); links and buttons inside are flattened to plain text, and inline formatting is not announced by screen readers (the hidden copy is plain text).

이 페이지는 AI로 번역되었습니다. 번역 문제 신고