Разбиение текста на символы применяется для создания поэлементной анимации букв, когда каждый символ управляется как отдельный объект. Такой подход позволяет реализовать эффекты появления текста, волны, каскады, шумовые деформации и синхронизацию с таймлайнами.
В контексте Motion One работа с текстом всегда опирается на
DOM-структуру: строка преобразуется в набор элементов, чаще всего
<span>, после чего каждый элемент анимируется
отдельно через animate, timeline или
stagger.
Ключевая особенность подхода:
Основной шаг — преобразование строки в массив символов и оборачивание каждого символа в DOM-элемент.
<div class="text">Motion One</div>
const el = document.querySelector(".text");
const chars = el.textContent.split("");
el.innerHTML = chars
.map(char => `<span class="char">${char}</span>`)
.join("");
Результат:
<span>Особенность:
или \u00A0)После разбиения важно зафиксировать базовые стили:
.text {
display: inline-block;
}
.char {
display: inline-block;
will-change: transform, opacity;
}
Ключевые моменты:
inline-block необходим для трансформаций каждого
символаwill-change уменьшает стоимость первой анимацииПосле подготовки DOM каждый символ становится объектом анимации.
Простейший сценарий появления:
import { animate } from "motion";
const chars = document.querySelectorAll(".char");
chars.forEach((char, i) => {
animate(
char,
{
opacity: [0, 1],
transform: ["translateY(20px)", "translateY(0px)"]
},
{
delay: i * 0.03,
duration: 0.4
}
);
});
Поведение:
В Motion One существует более компактный способ управления
последовательностью через stagger.
import { animate, stagger } from "motion";
animate(
".char",
{
opacity: [0, 1],
transform: ["translateY(20px)", "translateY(0px)"]
},
{
delay: stagger(0.03),
duration: 0.5
}
);
Особенности:
forEachconst splitText = (text) =>
text.split("").map(char => char === " " ? "\u00A0" : char);
Иногда требуется комбинированная структура:
const words = text.split(" ");
el.innerHTML = words
.map(word => {
const chars = word.split("");
return `<span class="word">
${chars.map(c => `<span class="char">${c}</span>`).join("")}
</span>`;
})
.join(" ");
Это позволяет:
Разбиение на символы позволяет применять разные траектории движения.
import { animate, stagger } from "motion";
animate(
".char",
{
opacity: [0, 1],
transform: [
"translateY(30px) scale(0.8)",
"translateY(0px) scale(1)"
]
},
{
delay: stagger(0.02, { start: 0.1 }),
duration: 0.6,
easing: "ease-out"
}
);
Здесь:
scale добавляет ощущение “сборки” текстаstagger.start задаёт задержку всей группыТаймлайны позволяют управлять всей последовательностью как единым сценарием.
import { timeline } from "motion";
timeline([
[".char", { opacity: [0, 1] }, { duration: 0.3, delay: stagger(0.02) }],
[".char", { y: [20, 0] }, { duration: 0.4 }, "-0.2"]
]);
Поведение:
Преимущества:
Каждый символ может иметь динамическую задержку:
animate(
".char",
(element, index) => ({
opacity: [0, 1],
transform: ["translateY(10px)", "translateY(0px)"]
}),
{
delay: (i) => i * 0.025,
duration: 0.4
}
);
Дополнительно возможно:
Разбиение на символы позволяет строить нелинейные анимации:
animate(
".char",
(element, i) => ({
transform: [
`translateY(${Math.sin(i) * 10}px)`,
"translateY(0px)"
]
}),
{
delay: stagger(0.02),
duration: 0.5
}
);
Такие эффекты используются для:
При работе с большим количеством символов важно учитывать нагрузку:
transform и opacity остаются единственными
анимируемыми свойствамиanimate(
".char",
{ opacity: [0, 1] },
{ delay: stagger(0.01), duration: 0.3 }
);
Малые задержки уменьшают визуальный разрыв и снижают нагрузку на планировщик кадров.
animate(
".char",
{
transform: ["translateY(40px)", "translateY(0px)"]
},
{
delay: stagger(0.04, { from: "center" }),
duration: 0.5
}
);
animate(
".char",
{ opacity: [0, 1] },
{ delay: stagger(0.06), duration: 0.1 }
);
animate(
".char",
{
transform: [
"translate(0px, 0px)",
"translate(50px, -30px)"
]
},
{
delay: stagger(0.02),
duration: 0.6,
direction: "alternate"
}
);
Разбиение символов часто связывается с событиями интерфейса:
button.addEventListener("mouseenter", () => {
animate(".char", {
transform: ["translateY(0px)", "translateY(-10px)"]
}, {
delay: stagger(0.02),
duration: 0.3
});
});
Такой подход позволяет: