Как анимировать слово StackOverflow
У меня есть слово StackOverflow:
<h1>StackOverflow</h1>
Как создать эффект анимации для этого слова, например, в виде волны и одновременно переливания одного цвета в другой?
Я представляю себе следующий сценарий анимации:
- Последовательная слева направо смена цвета букв
- Последовательное увеличение от исходного размера шрифта для каждой буквы
- Изменение размера шрифта в исходный размер в обратном порядке
- Обратная смена цвета букв в начальный цвет
- Зацикливание анимации
Как реализовать подобный сценарий такой анимации?
Рассматриваются любые варианты и ответы не только по метка, указанным в вопросе. Приемлемы любые другие текстовые эффекты, эффекты трансформации, абсолютно любые эффекты анимации без ограничений. А также по своему сценарию, но с использованием слова StackOverflow.
Данный вопрос создан в рамках и с целью обмена опытом между участниками сообщества. По инициативе @Alexandr_TT. Всех призываю принять участие и благодарю за участие!
Update 07.04. Учитывая, что в данном вопросе приняло участие уже несколько человек, считаю совершенно справедливым инициировать конкурс, назначив за него награду в виде репутации, которой я готов поделиться. А также огромная просьба ко всем участникам, пожалуйста, кратко прокомментировать свой код, свой сценарий решения вопроса. Этим подтверждаю, что мы все уважаем и соблюдаем действующие и обязательные для всех участников правила сообщества StackOverflow. Спасибо!
Update 14.04. Уважаемые участники! Конкурс подходит к завершению, и моя задача теперь вручить победителю галочку и баллы. Но как быть, если победители все? Каждый из участников на 100% выполнил конкурсное задание. А всего лишь нужно было просто анимировать слово, жаль только не все это понимают. Каждый ваш ответ индивидуален, и как показывает рейтинг, полезен для других участников. Следовательно, критерий моего выбора должен быть каким-то другим. Вы молодцы, вы профессионалы своего дела, и я очень горжусь тем, что меня окружает здесь такое количество талантливых людей, и я вам за это очень благодарен. Не хочу никого огорчать, но я должен сделать два клика напротив только одного ответа. К сожалению, таковы правила. Поэтому, у меня к вам предложение. Считаю его справедливым, и надеюсь, что вы одобрите мой выбор. Среди нас участников есть представительница прекрасной половины человечества. Дорогие друзья, уступим место? Хотелось бы знать ваше мнение. Спасибо вам за участие!
Ответы (12 шт):
Update 14.04.
body {
overflow: hidden;
margin: 0;
padding: 0;
background-color: #2da8cb;
background: rgb(24, 139, 172);
background: linear-gradient(45deg, gold 0%, lavender 99%);
}
div {
position: relative;
width: 100%;
height: 100vh;
margin: 0 0 0 20px;
}
h1,
h2 {
opacity: 1;
display: block;
position: absolute;
top: 0;
left: 0;
margin: 0;
padding: 0;
font-family: Helvetica, Arial;
font-weight: 900;
font-size: 6em;
letter-spacing: 0.01em;
line-height: 1em;
text-transform: uppercase;
text-shadow: 10px 10px 8px rgba(0, 0, 0, 0.15);
background: linear-gradient(45deg, red, orange, yellow, yellow, chocolate, yellow, orange, red, orange, yellow, yellow, chocolate, yellow, orange, red);
background-size: 250% 500%;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
text-fill-color: transparent;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-animation: animate 5s ease-out infinite;
animation: animate 5s ease-out infinite;
}
h2 {
top: 20px;
left: -100%;
opacity: 1;
font-size: 5em;
}
@keyframes animate {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
div:hover h1 {
left: -100%;
animation: animate 5s ease-out infinite, animate__hover__h1 2s ease-in-out;
}
div:hover h2 {
left: 0;
animation: animate 5s ease-out infinite, animate__hover__h2 4s ease-in-out;
}
@keyframes animate__hover__h1 {
from {
left: 0;
}
to {
left: -100%;
}
}
@keyframes animate__hover__h2 {
from {
left: -100%;
}
50% {
left: -100%;
}
to {
left: 0;
}
}
<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><h1>Stack<br>Overflow</h1><h2>Спасибо<br>за участие</h2></div>
Ответ на вопрос
Подобный сценарий я реализовал следующим образом:
- Заключил в заголовке
h1каждую букву словаStackOverflowв отдельный строчный элементspan. - Создал CSS анимацию и с помощью правила
@keyframesустановил общие ключевые кадры, включая в них нужные свойства (размер и цвет шрифта) при анимации для всех элементов. Так как подобный вариант анимации является средней сложности, поэтому вместо ключевых словfromиtoя использовал проценты. - Отдельно для каждого последующего элемента установил с помощью свойства
animation-delayвремя ожидания перед воспроизведением анимации по возрастанию в промежутке равном 0,1 секунде.
И вот, что у меня получилось в результате:
h1 {
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -50%);
}
@keyframes animate {
0% {
font-size: 60px;
color: #f48024;
}
10% {
font-size: 80px;
color: #242729
}
40% {
font-size: 40px;
color: #f48024;
}
70% {
font-size: 50px;
color: #f48024;
}
100% {
font-size: 60px;
color: #f48024;
}
}
h1>span {
font-family: Helvetica, Arial;
font-weight: 900;
font-size: 60px;
color: #f48024;
animation: animate 2.5s ease infinite;
}
h1>span:nth-child(2) {
animation-delay: .1s;
}
h1>span:nth-child(3) {
animation-delay: .2s;
}
h1>span:nth-child(4) {
animation-delay: .3s;
}
h1>span:nth-child(5) {
animation-delay: .4s;
}
h1>span:nth-child(6) {
animation-delay: .5s;
}
h1>span:nth-child(7) {
animation-delay: .6s;
}
h1>span:nth-child(8) {
animation-delay: .7s;
}
h1>span:nth-child(9) {
animation-delay: .8s;
}
h1>span:nth-child(10) {
animation-delay: .9s;
}
h1>span:nth-child(11) {
animation-delay: 1s;
}
h1>span:nth-child(12) {
animation-delay: 1.1s;
}
h1>span:nth-child(13) {
animation-delay: 1.2s;
}
<h1><span>S</span><span>t</span><span>a</span><span>c</span><span>k</span><span>O</span><span>v</span><span>e</span><span>r</span><span>f</span><span>l</span><span>o</span><span>w</span></h1>
Не люблю рамки, ни на что не претендую, просто вот вам прикольный эффект:
let s = 620;
document.body.innerHTML += `<canvas id="canvas"
width="${s}" height="${s/4}" ></canvas>`;
let ctx = canvas.getContext('2d');
let dots = [];
let mouse = {x:0, y:0}
let prevMouse = {x:0, y:0};
let startedAt = Date.now();
ctx.font = 'bold '+(s/7.5)+'px Arial';
ctx.fillStyle = 'red';
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillText('Стековерфлоу', s/2, s/8);
let mask = ctx.getImageData(0,0,s,s/4);
for (let i=0; dots.length < 3000; i++){
let x = s*Math.random();
let y1 = s*Math.random();
let y2 = -s*Math.random();
let offset = parseInt(y1)*s*4 + parseInt(x)*4;
if (mask.data[offset])
dots.push({x, y1, y2, speed: {x:0, y:0}, i})
}
ctx.fillStyle = 'black';
requestAnimationFrame(draw);
function draw(){
let t = Date.now() - startedAt;
ctx.clearRect(0,0,s,s);
dots.forEach(dot => {
let t1 = Math.min(1,(t-dot.x*4)/(500+2000*Math.abs(Math.sin(dot.i*4))));
t1 = t1*t1*t1
let x = dot.x;
let y = dot.y2 + (dot.y1-dot.y2) * t1;
let dx = mouse.x-x;
let dy = mouse.y-y;
let lenSq = dx*dx + dy*dy;
if (lenSq < 900) {
let a = Math.atan2(dx,dy)
let d = 30-Math.sqrt(lenSq);
dot.speed.x += dx/30*d;
dot.speed.y += dy/30*d;
}
x += dot.speed.x;
y += dot.speed.y;
dot.speed.x *= 0.92;
dot.speed.y *= 0.92;
ctx.fillRect(x-0.75, y-0.75, 1.5, 1.5);
});
prevMouse.x = mouse.x;
prevMouse.y = mouse.y;
requestAnimationFrame(draw);
}
addEventListener('mousemove', e => {
let bb = canvas.getBoundingClientRect();
mouse.x = e.x - bb.x;
mouse.y = e.y - bb.y;
});
['click','touchstart'].forEach(type =>
addEventListener(type, () => startedAt = Date.now()));
Чтобы расположить точки в виде букв, используется канва, на ней нарисован желаемый текст.
Первым шагом нееобхрдимо сгенерировать много точек, и каждую проверить на попадание в маску текста, сделать это можно проверив значение цвета пикселя по координатам точки, пусть это будет красный канал:
let mask = ctx.getImageData(0,0,s,s/4);
let red = mask.data[y*width*4 + x*4];
После того, как точки расставлены, можно их заанимировать, для этого каджую точку изначально поднимаем в область отрицательных значений у и со случайной скоростью двигаем вниз, до того, пока она не достигнет своего местоположения, так же к этому добавим задержку, пропорциональную x координате, чтобы анимация происходила слева-направо.
Финальный штрих - реакция на указатель: тут все относительно просто, считаем дистанцию от указателя до точки, и если она меньше определенного порога - меняем координату точки на значение, которое зависит от этой дистанции.
Код писался на скорую руку и там мало что будет разборчиво и хоть немного понятно, просто хотел поучаствовать :^
const h1 = document.querySelector('h1');
const h1Bound = h1.getBoundingClientRect();
charming(h1, {
tagName: 'div',
setClassName: () => {}
});
gsap.set(h1, {
width: h1Bound.width,
height: h1Bound.width
});
const divs = h1.querySelectorAll('div');
const angle = 360 / divs.length;
divs.forEach((div, index) => {
charming(div, {
tagName: 'span',
setClassName: () => {}
});
const span = div.querySelector('span');
gsap.set(div, { rotate: angle * index });
gsap.set(span, {
rotate: - angle * index,
opacity: 0,
scale: 0
})
});
const circle = document.querySelector('.circle');
gsap.set(circle, {
width: h1Bound.width,
height: h1Bound.width
});
const inner = circle.querySelector('.inner');
const config = {
holdDuration: 1 * 1000,
holdStatus: false,
holdComplete: false
};
circle.addEventListener('mouseenter', function() {
if (config.holdComplete) return;
gsap.to(this, {
scale: 1.1,
duration: 0.5
});
config.holdStatus = true;
const date = Date.now();
const callback = () => {
const difference = Date.now() - date;
const percentHold = 100 * difference / config.holdDuration;
gsap.set(inner, {
height: percentHold + '%'
});
if (percentHold >= 100) return holdHandle();
if (config.holdStatus) requestAnimationFrame(callback);
}
requestAnimationFrame(callback);
});
circle.addEventListener('mouseleave', function() {
if (config.holdComplete) return;
gsap.to(this, {
scale: 1,
duration: 0.5,
ease: "elastic.out(1, 0.4)"
});
config.holdStatus = false;
gsap.to(inner, {
duration: 0.1,
height: 0
});
});
const holdHandle = () => {
config.holdComplete = true;
config.holdStatus = false;
const tl = new gsap.timeline({
delay: 0.5
});
tl.resume();
tl.add(gsap.to(inner, {
duration: 1,
opacity: 0
}));
tl.add(gsap.to(circle, {
duration: 0.5,
scale: 1,
cursor: 'default'
}), '-=1');
divs.forEach(div => {
const span = div.querySelector('span');
tl.add(gsap.to(span, {
duration: 0.3,
opacity: 1,
scale: 1,
ease: "back.out(3)"
}), '-=0.2');
});
tl.play();
}
@import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@800&display=swap");
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
font-family: "Open Sans", sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
.circle {
width: 25vw;
height: 25vw;
background-color: #bcbbbb;
border-radius: 50%;
cursor: pointer;
overflow: hidden;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
.circle .logo {
width: 50%;
height: 50%;
object-fit: contain;
-webkit-mask-image: url("https://image.flaticon.com/icons/svg/2111/2111806.svg");
-webkit-mask-size: contain;
background-color: #f48023;
}
.circle .inner {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 0;
background-color: #f48023;
}
h1 {
/* display: none */
font-weight: 800;
font-size: 35px;
transform-origin: center;
transform-style: preserve-3d;
position: absolute;
pointer-events: none;
}
h1 div {
position: absolute;
display: block;
height: 100%;
width: 100%;
}
h1 div span {
display: block;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js"></script>
<script src="https://bundle.run/charming"></script>
<div class="circle">
<div class="logo"></div>
<div class="inner"></div>
</div>
<h1>STACKOVERFLOW</h1>
Тоже canvas
Автор скрипта: Ossama Rafique
var ImageParticles = {
density: 0,
produceDistance: 0,
baseRadius: 0,
maxLineThickness: 0,
reactionSensitivity: 0,
lineThickness: 0,
particles: [],
mouse: {
x: -1000,
y: -1000,
down: false
},
animation: null,
canvas: null,
context: null,
bgImage: null,
bgCanvas: null,
bgContext: null,
bgContextPixelData: null,
initialize: function(canvas_id, imageData, densityPara = 6, produceDistancePara = 28, baseRadiusPara = 2, maxLineThicknessPara = 1, reactionSensitivityPara = 2, lineThicknessPara = 1) {
this.canvas = document.getElementById(canvas_id);
this.context = canvas.getContext('2d');
this.context.globalCompositeOperation = "lighter";
this.canvas.width = window.innerWidth;
this.canvas.height = window.innerHeight;
this.canvas.style.display = 'block'
this.canvas.addEventListener('mousemove', this.pointerMove, false);
this.canvas.addEventListener('mousedown', this.pointerDown, false);
this.canvas.addEventListener('mouseup', this.pointerUp, false);
this.canvas.addEventListener('mouseout', this.pointerOut, false);
this.density = densityPara;
this.produceDistance = produceDistancePara;
this.baseRadius = baseRadiusPara;
this.maxLineThickness = maxLineThicknessPara;
this.reactionSensitivity = reactionSensitivityPara;
window.onresize = function(event) {
ImageParticles.canvas.width = window.innerWidth;
ImageParticles.canvas.height = window.innerHeight;
ImageParticles.onWindowResize();
}
this.getImageData(imageData);
},
makeParticles: function() {
this.particles = [];
var width, height, i, j;
var colors = this.bgContextPixelData.data;
for (i = 0; i < this.canvas.height; i += this.density) {
for (j = 0; j < this.canvas.width; j += this.density) {
var pixelPosition = (j + i * this.bgContextPixelData.width) * 4;
if (colors[pixelPosition] > 200 && (colors[pixelPosition + 1]) > 200 && (colors[pixelPosition + 2]) > 200 || colors[pixelPosition + 3] === 0) {
continue;
}
var color = 'rgba(' + colors[pixelPosition] + ',' + colors[pixelPosition + 2] + ',' + colors[pixelPosition + 3] + ',' + '1)';
this.particles.push({
x: j,
y: i,
originalX: j,
originalY: i,
color: color
});
}
}
},
updateparticles: function() {
var i, currentPoint, theta, distance;
for (i = 0; i < this.particles.length; i++) {
currentPoint = this.particles[i];
theta = Math.atan2(currentPoint.y - this.mouse.y, currentPoint.x - this.mouse.x);
if (this.mouse.down) {
distance = this.reactionSensitivity * 200 / Math.sqrt((this.mouse.x - currentPoint.x) * (this.mouse.x - currentPoint.x) +
(this.mouse.y - currentPoint.y) * (this.mouse.y - currentPoint.y));
} else {
distance = this.reactionSensitivity * 100 / Math.sqrt((this.mouse.x - currentPoint.x) * (this.mouse.x - currentPoint.x) +
(this.mouse.y - currentPoint.y) * (this.mouse.y - currentPoint.y));
}
currentPoint.x += Math.cos(theta) * distance + (currentPoint.originalX - currentPoint.x) * 0.10;
currentPoint.y += Math.sin(theta) * distance + (currentPoint.originalY - currentPoint.y) * 0.10;
}
},
produceLines: function() {
var i, j, currentPoint, otherPoint, distance, lineThickness;
for (i = 0; i < this.particles.length; i++) {
currentPoint = this.particles[i];
this.context.fillStyle = currentPoint.color;
for (j = 0; j < this.particles.length; j++) {
otherPoint = this.particles[j];
if (otherPoint == currentPoint) {
continue;
}
distance = Math.sqrt((otherPoint.x - currentPoint.x) * (otherPoint.x - currentPoint.x) +
(otherPoint.y - currentPoint.y) * (otherPoint.y - currentPoint.y));
if (distance <= this.produceDistance) {
this.context.beginPath();
this.context.moveTo(currentPoint.x, currentPoint.y);
}
}
}
},
produceparticles: function() {
var i, currentPoint;
for (i = 0; i < this.particles.length; i++) {
currentPoint = this.particles[i];
this.context.fillStyle = currentPoint.color;
this.context.beginPath();
this.context.arc(currentPoint.x, currentPoint.y, this.baseRadius, 0, Math.PI * 2, true);
this.context.closePath();
this.context.fill();
}
},
produce: function() {
this.animation = requestAnimationFrame(function() {
ImageParticles.produce()
});
this.remove();
this.updateparticles();
this.produceparticles();
},
remove: function() {
this.canvas.width = this.canvas.width;
},
getImageData: function(data) {
this.bgImage = new Image;
this.bgImage.src = data;
this.bgImage.onload = function() {
ImageParticles.produceImageParticles();
}
},
produceImageParticles: function() {
this.bgCanvas = document.createElement('canvas');
this.bgCanvas.width = this.canvas.width;
this.bgCanvas.height = this.canvas.height;
var newWidth, newHeight;
if (this.bgImage.width > this.bgCanvas.width - 100 || this.bgImage.height > this.bgCanvas.height - 100) {
var maxRatio = Math.max(this.bgImage.width / (this.bgCanvas.width - 100), this.bgImage.height / (this.bgCanvas.height - 100));
newWidth = this.bgImage.width / maxRatio;
newHeight = this.bgImage.height / maxRatio;
} else {
newWidth = this.bgImage.width;
newHeight = this.bgImage.height;
}
this.bgContext = this.bgCanvas.getContext('2d');
this.bgContext.drawImage(this.bgImage, (this.canvas.width - newWidth) / 2, (this.canvas.height - newHeight) / 2, newWidth, newHeight);
this.bgContextPixelData = this.bgContext.getImageData(0, 0, this.bgCanvas.width, this.bgCanvas.height);
this.makeParticles();
this.produce();
},
pointerDown: function(event) {
ImageParticles.mouse.down = true;
},
pointerUp: function(event) {
ImageParticles.mouse.down = false;
},
pointerMove: function(event) {
ImageParticles.mouse.x = event.offsetX || (event.layerX - ImageParticles.canvas.offsetLeft);
ImageParticles.mouse.y = event.offsetY || (event.layerY - ImageParticles.canvas.offsetTop);
},
pointerOut: function(event) {
ImageParticles.mouse.x = -1000;
ImageParticles.mouse.y = -1000;
ImageParticles.mouse.down = false;
},
onWindowResize: function() {
cancelAnimationFrame(this.animation);
this.produceImageParticles();
}
}
ImageParticles.initialize('canvas', 'data:image/png;base64,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');
canvas {
min-width: 100vw;
height: 100vh;
position: fixed;
}
<canvas id="canvas"></canvas>
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300&display=swap');
* {
margin: 0;
padding: 0;
}
body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
perspective: 1000px;
overflow: hidden;
background-color: #212121;
font-family: 'Roboto', sans-serif;
}
h1 {
--cb: cubic-bezier(0.71, 0.73, 0.58, 0.83);
--adur: 4s;
position: relative;
font-size: 10vw;
color: white;
-webkit-box-reflect: below 10px
linear-gradient(transparent, rgba(255, 255, 255, 0.1));
display: flex;
animation: whole_heading_animation var(--cb) var(--adur);
perspective: 1000px;
}
h1::after {
content: '';
position: absolute;
bottom: -10%;
left: 0;
display: block;
width: 100%;
height: 0.7vw;
background-color: #b3e5fc;
animation: draw_line 1.5s var(--adur) both;
}
span {
--cb-span: ease-out;
position: relative;
margin: 0 0.5vw;
animation-timing-function: var(--cb);
animation-duration: var(--adur);
transform-style: preserve-3d;
}
span::after,
span::before {
position: absolute;
left: 0;
top: 0;
}
span::after {
animation: after_animation var(--cb-span) var(--adur) both;
}
span::before {
animation: before_animation var(--cb-span) var(--adur) both;
}
span:nth-child(1) {
--tx-1: 30vw;
--ty-1: -10vw;
--tz-1: -3000px;
--rz-1: 1080deg;
--ry-1: 0deg;
--rx-1: 620deg;
animation-name: letter_1_animation;
}
span:nth-child(1)::after,
span:nth-child(1)::before {
content: 'S';
}
span:nth-child(2) {
--tx-2: 26vw;
--ty-2: 10vw;
--tz-2: -2600px;
--rz-2: 0deg;
--ry-2: -70deg;
--rx-2: -620deg;
animation-name: letter_2_animation;
}
span:nth-child(2)::after,
span:nth-child(2)::before {
content: 't';
}
span:nth-child(3) {
--tx-3: 22vw;
--ty-3: -8vw;
--tz-3: -2200px;
--rz-3: 0deg;
--ry-3: 40deg;
--rx-3: 750deg;
animation-name: letter_3_animation;
}
span:nth-child(3)::after,
span:nth-child(3)::before {
content: 'a';
}
span:nth-child(4) {
--tx-4: 18vw;
--ty-4: 10vw;
--tz-4: -1800px;
--rz-4: 0deg;
--ry-4: -50deg;
--rx-4: -900deg;
animation-name: letter_4_animation;
}
span:nth-child(4)::after,
span:nth-child(4)::before {
content: 'c';
}
span:nth-child(5) {
--tx-5: 14vw;
--ty-5: 0vw;
--tz-5: -1400px;
--rz-5: 80deg;
--ry-5: 1080deg;
--rx-5: -620deg;
animation-name: letter_5_animation;
}
span:nth-child(5)::after,
span:nth-child(5)::before {
content: 'k';
}
span:nth-child(6) {
--tx-6: 10vw;
--ty-6: -8vw;
--tz-6: -1000px;
--rz-6: 100deg;
--ry-6: 920deg;
--rx-6: -720deg;
animation-name: letter_6_animation;
}
span:nth-child(6)::after,
span:nth-child(6)::before {
content: 'O';
}
span:nth-child(7) {
--tx-7: 0vw;
--ty-7: -5vw;
--tz-7: -1000px;
--rz-7: 380deg;
--ry-7: -600deg;
--rx-7: 0deg;
animation-name: letter_7_animation;
}
span:nth-child(7)::after,
span:nth-child(7)::before {
content: 'v';
}
span:nth-child(8) {
--tx-8: -4vw;
--ty-8: 8vw;
--tz-8: -1400px;
--rz-8: 20deg;
--ry-8: 0deg;
--rx-8: 820deg;
animation-name: letter_8_animation;
}
span:nth-child(8)::after,
span:nth-child(8)::before {
content: 'e';
}
span:nth-child(9) {
--tx-9: -8vw;
--ty-9: 0vw;
--tz-9: -1800px;
--rz-9: -100deg;
--ry-9: 0deg;
--rx-9: -1020deg;
animation-name: letter_9_animation;
}
span:nth-child(9)::after,
span:nth-child(9)::before {
content: 'r';
}
span:nth-child(10) {
--tx-10: -12vw;
--ty-10: -4vw;
--tz-10: -2200px;
--rz-10: 100deg;
--ry-10: -500deg;
--rx-10: 1000deg;
animation-name: letter_10_animation;
}
span:nth-child(10)::after,
span:nth-child(10)::before {
content: 'f';
}
span:nth-child(11) {
--tx-11: -16vw;
--ty-11: -15vw;
--tz-11: -2600px;
--rz-11: 0deg;
--ry-11: 10deg;
--rx-11: -1020deg;
animation-name: letter_11_animation;
}
span:nth-child(11)::after,
span:nth-child(11)::before {
content: 'l';
}
span:nth-child(12) {
--tx-12: -20vw;
--ty-12: 0vw;
--tz-12: -3000px;
--rz-12: 0deg;
--ry-12: 60deg;
--rx-12: -620deg;
animation-name: letter_12_animation;
}
span:nth-child(12)::after,
span:nth-child(12)::before {
content: 'o';
}
span:nth-child(13) {
--tx-13: -24vw;
--ty-13: 10vw;
--tz-13: -3400px;
--rz-13: -1080deg;
--ry-13: 200deg;
--rx-13: 0deg;
animation-name: letter_13_animation;
}
span:nth-child(13)::after,
span:nth-child(13)::before {
content: 'w';
}
@keyframes whole_heading_animation {
from {
transform: translateZ(-8000px) scale(0);
opacity: 0;
}
25% {
transform: translateZ(-500px);
opacity: 1;
}
to {
transform: translateZ(0);
}
}
@keyframes draw_line {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
}
}
@keyframes letter_1_animation {
from {
transform: translate3d(var(--tx-1), var(--ty-1), var(--tz-1))
rotateX(var(--rx-1)) rotateY(var(--ry-1)) rotateZ(var(--rz-1));
}
35% {
transform: translate3d(
calc(var(--tx-1) / 3),
calc(var(--ty-1) / 3),
calc(var(--tz-1) / 3)
)
rotateX(calc(var(--rx-1) / 3)) rotateY(calc(var(--ry-1) / 3))
rotateZ(calc(var(--rz-1) / 3));
}
}
@keyframes letter_2_animation {
from {
transform: translate3d(var(--tx-2), var(--ty-2), var(--tz-2))
rotateX(var(--rx-2)) rotateY(var(--ry-2)) rotateZ(var(--rz-2));
}
35% {
transform: translate3d(
calc(var(--tx-2) / 3),
calc(var(--ty-2) / 3),
calc(var(--tz-2) / 3)
)
rotateX(calc(var(--rx-2) / 3)) rotateY(calc(var(--ry-2) / 3))
rotateZ(calc(var(--rz-2) / 3));
}
}
@keyframes letter_3_animation {
from {
transform: translate3d(var(--tx-3), var(--ty-3), var(--tz-3))
rotateX(var(--rx-3)) rotateY(var(--ry-3)) rotateZ(var(--rz-3));
}
35% {
transform: translate3d(
calc(var(--tx-3) / 3),
calc(var(--ty-3) / 3),
calc(var(--tz-3) / 3)
)
rotateX(calc(var(--rx-3) / 3)) rotateY(calc(var(--ry-3) / 3))
rotateZ(calc(var(--rz-3) / 3));
}
}
@keyframes letter_4_animation {
from {
transform: translate3d(var(--tx-4), var(--ty-4), var(--tz-4))
rotateX(var(--rx-4)) rotateY(var(--ry-4)) rotateZ(var(--rz-4));
}
35% {
transform: translate3d(
calc(var(--tx-4) / 3),
calc(var(--ty-4) / 3),
calc(var(--tz-4) / 3)
)
rotateX(calc(var(--rx-4) / 3)) rotateY(calc(var(--ry-4) / 3))
rotateZ(calc(var(--rz-4) / 3));
}
}
@keyframes letter_5_animation {
from {
transform: translate3d(var(--tx-5), var(--ty-5), var(--tz-5))
rotateX(var(--rx-5)) rotateY(var(--ry-5)) rotateZ(var(--rz-5));
}
35% {
transform: translate3d(
calc(var(--tx-5) / 3),
calc(var(--ty-5) / 3),
calc(var(--tz-5) / 3)
)
rotateX(calc(var(--rx-5) / 3)) rotateY(calc(var(--ry-5) / 3))
rotateZ(calc(var(--rz-5) / 3));
}
}
@keyframes letter_6_animation {
from {
transform: translate3d(var(--tx-6), var(--ty-6), var(--tz-6))
rotateX(var(--rx-6)) rotateY(var(--ry-6)) rotateZ(var(--rz-6));
}
35% {
transform: translate3d(
calc(var(--tx-6) / 3),
calc(var(--ty-6) / 3),
calc(var(--tz-6) / 3)
)
rotateX(calc(var(--rx-6) / 3)) rotateY(calc(var(--ry-6) / 3))
rotateZ(calc(var(--rz-6) / 3));
}
}
@keyframes letter_7_animation {
from {
transform: translate3d(var(--tx-7), var(--ty-7), var(--tz-7))
rotateX(var(--rx-7)) rotateY(var(--ry-7)) rotateZ(var(--rz-7));
}
35% {
transform: translate3d(
calc(var(--tx-7) / 3),
calc(var(--ty-7) / 3),
calc(var(--tz-7) / 3)
)
rotateX(calc(var(--rx-7) / 3)) rotateY(calc(var(--ry-7) / 3))
rotateZ(calc(var(--rz-7) / 3));
}
}
@keyframes letter_8_animation {
from {
transform: translate3d(var(--tx-8), var(--ty-8), var(--tz-8))
rotateX(var(--rx-8)) rotateY(var(--ry-8)) rotateZ(var(--rz-8));
}
35% {
transform: translate3d(
calc(var(--tx-8) / 3),
calc(var(--ty-8) / 3),
calc(var(--tz-8) / 3)
)
rotateX(calc(var(--rx-8) / 3)) rotateY(calc(var(--ry-8) / 3))
rotateZ(calc(var(--rz-8) / 3));
}
}
@keyframes letter_9_animation {
from {
transform: translate3d(var(--tx-9), var(--ty-9), var(--tz-9))
rotateX(var(--rx-9)) rotateY(var(--ry-9)) rotateZ(var(--rz-9));
}
35% {
transform: translate3d(
calc(var(--tx-9) / 3),
calc(var(--ty-9) / 3),
calc(var(--tz-9) / 3)
)
rotateX(calc(var(--rx-9) / 3)) rotateY(calc(var(--ry-9) / 3))
rotateZ(calc(var(--rz-9) / 3));
}
}
@keyframes letter_10_animation {
from {
transform: translate3d(var(--tx-10), var(--ty-10), var(--tz-10))
rotateX(var(--rx-10)) rotateY(var(--ry-10)) rotateZ(var(--rz-10));
}
35% {
transform: translate3d(
calc(var(--tx-10) / 3),
calc(var(--ty-10) / 3),
calc(var(--tz-10) / 3)
)
rotateX(calc(var(--rx-10) / 3)) rotateY(calc(var(--ry-10) / 3))
rotateZ(calc(var(--rz-10) / 3));
}
}
@keyframes letter_11_animation {
from {
transform: translate3d(var(--tx-11), var(--ty-11), var(--tz-11))
rotateX(var(--rx-11)) rotateY(var(--ry-11)) rotateZ(var(--rz-11));
}
35% {
transform: translate3d(
calc(var(--tx-11) / 3),
calc(var(--ty-11) / 3),
calc(var(--tz-11) / 3)
)
rotateX(calc(var(--rx-11) / 3)) rotateY(calc(var(--ry-11) / 3))
rotateZ(calc(var(--rz-11) / 3));
}
}
@keyframes letter_12_animation {
from {
transform: translate3d(var(--tx-12), var(--ty-12), var(--tz-12))
rotateX(var(--rx-12)) rotateY(var(--ry-12)) rotateZ(var(--rz-12));
}
35% {
transform: translate3d(
calc(var(--tx-12) / 3),
calc(var(--ty-12) / 3),
calc(var(--tz-12) / 3)
)
rotateX(calc(var(--rx-12) / 3)) rotateY(calc(var(--ry-12) / 3))
rotateZ(calc(var(--rz-12) / 3));
}
}
@keyframes letter_13_animation {
from {
transform: translate3d(var(--tx-13), var(--ty-13), var(--tz-13))
rotateX(var(--rx-13)) rotateY(var(--ry-13)) rotateZ(var(--rz-13));
}
35% {
transform: translate3d(
calc(var(--tx-13) / 3),
calc(var(--ty-13) / 3),
calc(var(--tz-13) / 3)
)
rotateX(calc(var(--rx-13) / 3)) rotateY(calc(var(--ry-13) / 3))
rotateZ(calc(var(--rz-13) / 3));
}
}
@keyframes before_animation {
10% {
transform: translate3d(0, 0, 0) scale(1);
}
70% {
transform: translate3d(-0, -10vw, -3vw) scale(0.9) skew(-20deg);
opacity: 0.1;
}
to {
transform: translate3d(-0.4vw, -0.4vw, -0.4vw) scale(1);
}
}
@keyframes after_animation {
10% {
transform: translate3d(0, 0, 0) scale(1);
}
70% {
transform: translate3d(2vw, 0, 3vw) scale(2) skew(-20deg);
opacity: 0.5;
}
to {
transform: translate3d(0.4vw, 0.4vw, 0.4vw) scale(1);
}
}
<h1>
<span>S</span>
<span>t</span>
<span>a</span>
<span>c</span>
<span>k</span>
<span>O</span>
<span>v</span>
<span>e</span>
<span>r</span>
<span>f</span>
<span>l</span>
<span>o</span>
<span>w</span>
</h1>
- Анимация обводки контуров букв
stroke-dasharray
Используется гугловский шрифт Tangerine
Этот способ интересен прежде всего тем, что не нужно для каждой буквы вычислять stroke-dasharray и писать анимацию для каждой буквы.
Достаточно одной для всей фразы
<animate begin="svg1.click" dur="15s" repeatCount="indefinite"
attributeName="stroke-dashoffset"
values="500;0;0;500;500" fill="freeze" restart="whenNotActive" />
<head>
<link rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Tangerine">
<style>
body {
font-family: 'Tangerine', serif;
font-size: 150px;
font-weigth:bold;
}
</style>
</head>
<body>
<svg id="svg1" version="1.1" width="500" height="200"
viewBox="0 0 500 200"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" style="background:dodgerblue">
<text stroke-dasharray="500 500" stroke-dashoffset="500" fill="none" stroke="white" stroke-width="2" dy="1em" >
Stackoverflow
<animate begin="svg1.click" dur="15s" repeatCount="indefinite"
attributeName="stroke-dashoffset"
values="500;0;0;500;500" fill="freeze" restart="whenNotActive" />
</text>
</svg>
</body>
- Анимация обводки букв из одной точки двумя линиями Эта техника тоже интересна и полезна для реализации текстовых эффектов. Ведь даже рисование одной линией контура смотрится довольно интересно, а если двумя линиями рисовать, то это будет смотреться вдвое интересней.
Суть этого метода заключается в использовании двух пар атрибута stroke-dasharray
Для первой буквы полная длина 355px
Скрываем линию до анимации
0,177.5 0,177.5 черта=0 пробел = 177.5 черта=0 пробел = 177.5 итого
вся линия скрыта
Показываем линию:
0,0,355,0 - черта во второй паре атрибутов равна полной длине 355 поэтому линия полностью видна
<animate id="t1" attributeName="stroke-dasharray" begin="s1.end" dur="2s"
values="0,177.5 0,177.5;0,0,355,0" fill="freeze" />
</path>
<style>
.el{
stroke:cyan;
stroke-width:2;
fill:none;
}
.container {
width:100vw;
height:100vh;
}
</style>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.js"></script>
<div class="line-drawing-demo">
<svg id="svg1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="600" height="200" viewBox="0 0 350 100" style="border:1px solid grey;">
<rect width="100%" height="100%" fill="#111111" />
<g class="lines" >
<path class="el" stroke-dasharray="0,317" d="m59.3 12.8 6.4 20.3q-5.3-4.5-9.5-6.9-4.2-2.4-6.8-2.4-2 0-3.1 1.1-1.1 1.1-1.1 3 0 2.4 2.4 4.3 2.4 1.9 7.5 3.4 11.3 3.4 15.3 6.7 4 3.3 4 8.4 0 8.3-8.1 13.6-8.1 5.3-20.9 5.3-6.5 0-12.9-2.3-6.4-2.4-11.8-6.7V69.6L13.9 49.3q4.7 3.8 9.2 5.9 4.6 2 8.3 2 4 0 6.2-1.7 2.2-1.7 2.2-4.7 0-2.8-1.8-4.5-1.8-1.7-8-3.6-9-2.9-13.5-6.8-4.4-4-4.4-9.3 0-6.7 5.9-11 5.9-4.3 15.4-4.3 6.2 0 12.2 2.2 5.9 2.2 12.2 6.9z" >
<animate id="s1" attributeName="stroke-dasharray" begin="svg1.click" dur="2s" values="0,158.5 0,158.5;0,0,317,0" fill="freeze" />
</path>
<path class="el" stroke-dasharray="0,355" d="M136.9 11.5 118.6 47.3 116.2 20 105.6 61.9 121.7 69.6 87 67.7 98.2 64.3 85.5 19.2 81.9 52.3 74.5 15.7Z" >
<animate id="t1" attributeName="stroke-dasharray" begin="s1.end" dur="2s"
values="0,177.5 0,177.5;0,0,355,0" fill="freeze" />
</path>
<path class="el" stroke-dasharray="0,342" d="m171 38-5.3-15.1-5.1 18.6zm30.5 21.7-33 10.2 8.4-11.3-2.4-8.4-12.3 0.9-3.6 9.8 5.3 4.2-35.9 2.6 8.7-6.5 11.6-45.2-6.6-4.3 44.5 1.5-10.4 3.7 18.2 40.6z" >
<animate id="a1" attributeName="stroke-dasharray" begin="t1.end" dur="2s"
values="0,171 0,171;0,0,342,0" fill="freeze" />
</path>
<path class="el" stroke-dasharray="0,270" d="m256.7 40.5-4 26.3-1.6-10.7q-5.5 6.5-12.1 9.9-6.6 3.4-13.8 3.4-4.7 0-9.2-1.9-4.5-2-8.1-5.7-4.3-4.3-6.6-9.7-2.3-5.4-2.3-11 0-7.8 3.3-14.2 3.3-6.4 9.3-10.5 4-2.7 8.7-4 4.7-1.3 10.1-1.3 5 0 9.2 1.6 4.3 1.6 8 4.7l2.2-6.3 3.9 20.7q-2.5-3.6-5.9-5.5-3.4-1.9-7.3-1.9-4.5 0-7.3 2.7-2.8 2.6-2.8 7 0 4.6 3.4 7.8 3.4 3.1 8.6 3.1 3.2 0 6.6-1.1 3.4-1.1 7.4-3.4z" >
<animate id="c1" attributeName="stroke-dasharray" begin="a1.end" dur="2s"
values="0,135 0,135;0,0,270,0" fill="freeze" />
</path>
<path class="el" stroke-dasharray="0,375" d="m329.1 68.8-30.1-3.7 6-3.9-17.6-18.1-2 0.4 4.1 20.1 6.4 6.8-38.1-4.3 6.8-5 3.6-41.2-7.4-4.7 30.5-3.7-8.5 6.6 2.5 16 13.4-15.9-3.7-4.6 27.7 1-19.7 15.8z" >
<animate id="c1" attributeName="stroke-dasharray" begin="c1.end" dur="2s"
values="0,187.5 0,187.5;0,0,375,0" fill="freeze" />
</path>
</g>
</svg>
</div>
- Анимация обводки, как в предыдущем примере плюс анимация собирания букв
.el{
stroke:yellow;
stroke-width:2;
fill:none;
}
.container {
width:100vw;
height:100vh;
}
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/animejs/2.2.0/anime.js"></script>
<div class="line-drawing-demo">
<svg id="svg1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="600" height="200" viewBox="0 0 350 100" style="border:1px solid grey;">
<rect width="100%" height="100%" fill="#111111" />
<g class="lines" >
<!-- Буква "S" -->
<path class="el" stroke-dasharray="0,317" d="m59.3 12.8 6.4 20.3q-5.3-4.5-9.5-6.9-4.2-2.4-6.8-2.4-2 0-3.1 1.1-1.1 1.1-1.1 3 0 2.4 2.4 4.3 2.4 1.9 7.5 3.4 11.3 3.4 15.3 6.7 4 3.3 4 8.4 0 8.3-8.1 13.6-8.1 5.3-20.9 5.3-6.5 0-12.9-2.3-6.4-2.4-11.8-6.7V69.6L13.9 49.3q4.7 3.8 9.2 5.9 4.6 2 8.3 2 4 0 6.2-1.7 2.2-1.7 2.2-4.7 0-2.8-1.8-4.5-1.8-1.7-8-3.6-9-2.9-13.5-6.8-4.4-4-4.4-9.3 0-6.7 5.9-11 5.9-4.3 15.4-4.3 6.2 0 12.2 2.2 5.9 2.2 12.2 6.9z" >
<animate id="s1" attributeName="stroke-dasharray" begin="svg1.click" dur="1.5s" values="0,158.5 0,158.5;0,0,317,0" fill="freeze" restart="whenNotActive" />
<animateTransform id="st" attributeName="transform" type="translate" begin="k1.end" dur="1s" values="0;100" fill="freeze" restart="whenNotActive" />
<animateTransform id="sBack" attributeName="transform" type="translate" begin="kt.end+1s" dur="1s" values="0;0" fill="freeze" restart="whenNotActive" />
</path>
<!-- Буква "T" -->
<path class="el" stroke-dasharray="0,355" d="M136.9 11.5 118.6 47.3 116.2 20 105.6 61.9 121.7 69.6 87 67.7 98.2 64.3 85.5 19.2 81.9 52.3 74.5 15.7Z" >
<animate id="t1" attributeName="stroke-dasharray" begin="s1.end" dur="1.5s"
values="0,177.5 0,177.5;0,0,355,0" fill="freeze" restart="whenNotActive" />
<animateTransform id="tt" attributeName="transform" type="translate"
begin="st.end" dur="1s" values="0;50" fill="freeze" restart="whenNotActive" />
<animateTransform id="tBack" attributeName="transform" type="translate" begin="sBack.end+1s" dur="1s" values="0;0" fill="freeze" restart="whenNotActive" />
</path>
<!-- Буква "A" -->
<path class="el" stroke-dasharray="0,342" d="m171 38-5.3-15.1-5.1 18.6zm30.5 21.7-33 10.2 8.4-11.3-2.4-8.4-12.3 0.9-3.6 9.8 5.3 4.2-35.9 2.6 8.7-6.5 11.6-45.2-6.6-4.3 44.5 1.5-10.4 3.7 18.2 40.6z" >
<animate id="a1" attributeName="stroke-dasharray" begin="t1.end" dur="1.5s"
values="0,171 0,171;0,0,342,0" fill="freeze" restart="whenNotActive" />
<animateTransform id="at" attributeName="transform" type="translate" begin="tt.end" dur="0.1s" values="0;0" fill="freeze" restart="whenNotActive" />
</path>
<!-- Буква "C" -->
<path class="el" stroke-dasharray="0,270" d="m256.7 40.5-4 26.3-1.6-10.7q-5.5 6.5-12.1 9.9-6.6 3.4-13.8 3.4-4.7 0-9.2-1.9-4.5-2-8.1-5.7-4.3-4.3-6.6-9.7-2.3-5.4-2.3-11 0-7.8 3.3-14.2 3.3-6.4 9.3-10.5 4-2.7 8.7-4 4.7-1.3 10.1-1.3 5 0 9.2 1.6 4.3 1.6 8 4.7l2.2-6.3 3.9 20.7q-2.5-3.6-5.9-5.5-3.4-1.9-7.3-1.9-4.5 0-7.3 2.7-2.8 2.6-2.8 7 0 4.6 3.4 7.8 3.4 3.1 8.6 3.1 3.2 0 6.6-1.1 3.4-1.1 7.4-3.4z" >
<animate id="c1" attributeName="stroke-dasharray" begin="a1.end" dur="1.5s"
values="0,135 0,135;0,0,270,0" fill="freeze" restart="whenNotActive" />
<animateTransform id="ct" attributeName="transform" type="translate" begin="at.end" dur="1s" values="0;-50" fill="freeze" restart="whenNotActive" />
<animateTransform id="cBack" attributeName="transform" type="translate" begin="sBack.end+0.5s" dur="1s" values="0;0" fill="freeze" restart="whenNotActive" />
</path>
<!-- Буква "K" -->
<path class="el" stroke-dasharray="0,375" d="m329.1 68.8-30.1-3.7 6-3.9-17.6-18.1-2 0.4 4.1 20.1 6.4 6.8-38.1-4.3 6.8-5 3.6-41.2-7.4-4.7 30.5-3.7-8.5 6.6 2.5 16 13.4-15.9-3.7-4.6 27.7 1-19.7 15.8z" >
<animate id="k1" attributeName="stroke-dasharray" begin="c1.end" dur="1.5s"
values="0,187.5 0,187.5;0,0,375,0" fill="freeze" restart="whenNotActive" />
<animateTransform id="kt" attributeName="transform" type="translate" begin="ct.end" dur="1s" values="0;-100" fill="freeze" restart="whenNotActive" />
<animateTransform id="kBack" attributeName="transform" type="translate" begin="kt.end+1s" dur="1s" values="0;0" fill="freeze" restart="whenNotActive" />
</path>
</g>
</svg>
</div>
- Анимация хаотичного движения букв, вертикальный финиш сборки букв в слово
<style>
#text1 {
fill:yellow;
}
</style>
<svg id="svg1" width="70%" height="70%" viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="tiny"
xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMinYMin meet">
<defs>
<linearGradient id="grad"
x1="0" y1="0" x2="0" y2="100%"
gradientUnits="userSpaceOnUse">
<stop offset="2%" stop-color="black" />
<stop offset="75%" stop-color="red" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#grad)" />
<text x="200 " y="500"
font-size="90" fill="#E4E4E4" stroke-width="1" stroke="#E4E4E4">Stackoverflow</text>
<text id="text1" x="200" y="500"
font-size="90">Stackoverflow</text>
<animate xlink:href="#text1"
attributeName="x"
attributeType="XML"
values="200 233 266 299 332 365 400 431 464 497 530 563 596;
100 600 200 365 700 465 465 563 530 398 431 850 900;
200 500 900 950 150 531 300 620 150 266 365 650 900;
332 233 820 300 800 633 200 670 300 850 800 530 266;
464 900 900 900 820 670 430 900 530 600 233 365 100;
332 100 100 100 500 100 800 563 900 700 900 100 100;
200 233 266 299 332 365 400 431 464 497 530 563 596"
dur="3s"
begin="svg1.click"
repeatCount="2" />
<animate xlink:href="#text1"
attributeName="y"
attributeType="XML"
values="500 500 500 500 500 500 500 500 500 500 500 500 500;
100 200 850 100 250 175 750 100 750 720 850 500 50;
100 600 600 250 200 450 50 200 520 550 300 300 750;
500 100 650 650 600 150 550 50 150 550 200 550 400;
800 300 100 750 150 650 75 350 550 700 755 120 800;
800 600 300 150 750 350 700 650 200 250 500 650 100;
500 500 500 500 500 500 500 500 500 500 500 500 500"
dur="4s"
begin="svg1.click"
repeatCount="2" />
</svg>
- Горизонтальный финиш сборки букв в слово
<style>
#text1 {
fill:#D0FF00;
}
</style>
<svg id="svg1" width="50%" height="50%" viewBox="0 0 1000 1000"
xmlns="http://www.w3.org/2000/svg" version="1.1" baseProfile="tiny"
xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMinYMin meet">
<title>Animation of text x and y attributes</title>
<defs>
<linearGradient id="grad"
x1="0" y1="0" x2="0" y2="100%"
gradientUnits="userSpaceOnUse">
<stop offset="2%" stop-color="#151515" />
<stop offset="70%" stop-color="yellowgreen" />
</linearGradient>
</defs>
<rect width="100%" height="100%" fill="url(#grad)" />
<text x="200 " y="500"
font-size="90" fill="#d3d3d3" stroke-width="1" stroke="#d3d3d3">Stackoverflow</text>
<text id="text1" x="200" y="500"
font-size="90">Stackoverflow</text>
<animate xlink:href="#text1"
attributeName="x"
attributeType="XML"
values="200 233 266 299 332 365 400 431 464 497 530 563 596;
100 600 200 365 700 465 465 563 530 398 431 850 900;
200 500 900 950 150 531 300 620 150 266 365 650 900;
332 233 820 300 800 633 200 670 300 850 800 530 266;
464 900 900 900 820 670 430 900 530 600 233 365 100;
332 100 100 100 500 100 800 563 900 700 900 100 100;
200 233 266 299 332 365 400 431 464 497 530 563 596"
dur="4s"
begin="svg1.click"
repeatCount="2" />
<animate xlink:href="#text1"
attributeName="y"
attributeType="XML"
values="500 500 500 500 500 500 500 500 500 500 500 500 500;
100 200 850 100 250 175 750 100 750 720 850 500 50;
100 600 600 250 200 450 50 200 520 550 300 300 750;
500 100 650 650 600 150 550 50 150 550 200 550 400;
800 300 100 750 150 650 75 350 550 700 755 120 800;
800 600 300 150 750 350 700 650 200 250 500 650 100;
500 500 500 500 500 500 500 500 500 500 500 500 500"
dur="3s"
begin="svg1.click"
repeatCount="2" />
</svg>
Такой короткий вариант:
* {
box-sizing: border-box;
}
html,
body,
.project {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.word-x {
position: absolute;
top: 50%;
left: 50%;
margin-top: -125px;
margin-left: -125px;
height: 250px;
width: 250px;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-backface-visibility: visible;
backface-visibility: visible;
-webkit-transform-origin: 50% 50%;
transform-origin: 50% 50%;
-webkit-transform: rotateY(0deg) rotateZ(0deg) rotateX(0deg);
transform: rotateY(0deg) rotateZ(0deg) rotateX(0deg);
-webkit-animation: word-rotate 25s linear infinite;
animation: word-rotate 25s linear infinite;
}
.word-x .word {
position: absolute;
width: 250px;
height: 250px;
margin-top: -125px;
margin-left: -125px;
top: 50%;
left: 50%;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-backface-visibility: visible;
backface-visibility: visible;
-webkit-transform-origin: 50% 50%;
transform-origin: 50% 50%;
}
.word-x .word .s {
position: absolute;
-webkit-transform-origin: 0% 50%;
transform-origin: 0% 50%;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-backface-visibility: visible;
backface-visibility: visible;
top: 0%;
left: 0%;
}
.word-x .word .s svg {
position: absolute;
top: 0;
left: 0%;
width: 250px;
height: 250px;
-webkit-transform-style: preserve-3d;
transform-style: preserve-3d;
-webkit-backface-visibility: visible;
backface-visibility: visible;
-webkit-transform-origin: 50% 50%;
transform-origin: 50% 50%;
}
.word-x .word .s svg .p {
fill: rgba(0, 0, 0, 0.1);
stroke: rgba(0, 0, 0, 0.1);
-webkit-animation: pulse 25s linear infinite;
animation: pulse 25s linear infinite;
}
.word .s:nth-of-type(1) {
-webkit-transform: rotateY(0deg) translateZ(1px);
transform: rotateY(0deg) translateZ(1px);
}
.word .s:nth-of-type(1) .p {
-webkit-animation-delay: 0.1s;
animation-delay: 0.1s;
}
.word .s:nth-of-type(2) {
-webkit-transform: rotateY(0deg) translateZ(2px);
transform: rotateY(0deg) translateZ(2px);
}
.word .s:nth-of-type(2) .p {
-webkit-animation-delay: 0.2s;
animation-delay: 0.2s;
}
.word .s:nth-of-type(3) {
-webkit-transform: rotateY(0deg) translateZ(3px);
transform: rotateY(0deg) translateZ(3px);
}
.word .s:nth-of-type(3) .p {
-webkit-animation-delay: 0.3s;
animation-delay: 0.3s;
}
.word .s:nth-of-type(4) {
-webkit-transform: rotateY(0deg) translateZ(4px);
transform: rotateY(0deg) translateZ(4px);
}
.word .s:nth-of-type(4) .p {
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
}
.word .s:nth-of-type(5) {
-webkit-transform: rotateY(0deg) translateZ(5px);
transform: rotateY(0deg) translateZ(5px);
}
.word .s:nth-of-type(5) .p {
-webkit-animation-delay: 0.5s;
animation-delay: 0.5s;
}
.word .s:nth-of-type(6) {
-webkit-transform: rotateY(0deg) translateZ(6px);
transform: rotateY(0deg) translateZ(6px);
}
.word .s:nth-of-type(6) .p {
-webkit-animation-delay: 0.6s;
animation-delay: 0.6s;
}
.word .s:nth-of-type(7) {
-webkit-transform: rotateY(0deg) translateZ(7px);
transform: rotateY(0deg) translateZ(7px);
}
.word .s:nth-of-type(7) .p {
-webkit-animation-delay: 0.7s;
animation-delay: 0.7s;
}
.word .s:nth-of-type(8) {
-webkit-transform: rotateY(0deg) translateZ(8px);
transform: rotateY(0deg) translateZ(8px);
}
.word .s:nth-of-type(8) .p {
-webkit-animation-delay: 0.8s;
animation-delay: 0.8s;
}
.word .s:nth-of-type(9) {
-webkit-transform: rotateY(0deg) translateZ(9px);
transform: rotateY(0deg) translateZ(9px);
}
.word .s:nth-of-type(9) .p {
-webkit-animation-delay: 0.9s;
animation-delay: 0.9s;
}
.word .s:nth-of-type(10) {
-webkit-transform: rotateY(0deg) translateZ(10px);
transform: rotateY(0deg) translateZ(10px);
}
.word .s:nth-of-type(10) .p {
-webkit-animation-delay: 1s;
animation-delay: 1s;
}
@-webkit-keyframes pulse {
50% {
fill: rgba(0, 0, 0, 0.08);
stroke: rgba(0, 0, 0, 0.08);
}
}
@keyframes pulse {
50% {
fill: rgba(0, 0, 0, 0.08);
stroke: rgba(0, 0, 0, 0.08);
}
}
@-webkit-keyframes word-rotate {
50% {
-webkit-transform: rotateY(360deg) rotateZ(36deg) rotateX(18deg);
transform: rotateY(360deg) rotateZ(36deg) rotateX(18deg);
}
}
@keyframes word-rotate {
50% {
-webkit-transform: rotateY(360deg) rotateZ(36deg) rotateX(18deg);
transform: rotateY(360deg) rotateZ(36deg) rotateX(18deg);
}
}
<div class="project">
<div class="word-x">
<div class="word">
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
<div class="s"><svg width="154" height="182" viewBox="0 0 154 182" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M129.781 165.477V116.945H145.958V181.656H0.461548V116.945H16.6379V165.477H129.781Z" fill="#BCBBBC"/>
<path d="M32.8323 149.298H113.714V133.122H32.8323V149.298ZM105.323 0.349084L92.3429 10.0028L140.614 74.9019L153.594 65.248L105.323 0.349084ZM65.2014 38.6106L127.352 90.3718L137.704 77.9411L75.5537 26.1802L65.2014 38.6106ZM44.9115 74.581L118.232 108.728L125.062 94.0641L51.7409 59.917L44.9115 74.581ZM34.4385 112.481L113.592 129.117L116.919 113.286L37.7655 96.6501L34.4385 112.481Z" fill="#F48023"/>
</div>
</div>
</div>
</div>
Анимация раскраски и оживления слова STACK
Мы собираемся все вместе на Stackoverflow, со всех русскоговорящих территорий, чтобы получить удовольствие от любимого занятия программированием. Вот эту идею в условиях конкурса я попробовал выразить в анимации.
Буквы S T A C K двигаются по одной с различных направлений карты, чтобы собраться в слово STACK и уже вместе продолжить движение,
Для того, чтобы взаимное расположение карты и маршрутов движения, были всегда жестко связаны я использовал следующий приём:
Загрузил карту в векторный редактор и нарисовал маршруты для каждой буквы (красные линии) и маршрут движения для слова целиком.(синяя линия)
Скопировал все path маршрутов в отдельный файл, в котором буду делать анимацию движения.
У каждой буквы несколько анимаций:
- движение вдоль траектории
<animateMotion xlink:href="#Sgroup" begin="btn1.click" dur="36s" restart="whenNotActive">
<mpath xlink:href="#Strack" />
</animateMotion>
- Покачивание самой буквы влево-вправо при ходьбе
<!-- Покачивание буквы S -->
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="2s"
values="
0, 43.25,51.14;
10, 43.25,51.14;
0, 43.25,51.14;
0, 43.25,51.14"
fill="freeze"
repeatCount="indefinite"
additive="sum"
restart="whenNotActive" />
- Имитация движения ног буквы при ходьбе
<animateTransform attributeName="transform" type="rotate" begin="btn1.click" dur="1s"
values="0, 29.41,64.39;30, 29.41,64.39;0, 29.41,64.39" fill="freeze" repeatCount="indefinite" />
- Чтобы цветные буквы при движении не сливались с одинаковыми цветными зонами карт, добавил тень с помощью SVG filter
<filter id="dropShadow">
<feDropShadow dx="4" dy="4" stdDeviation="8" result="shadow"/>
<feComposite in2="mask" in="shadow" operator="in" result="comp" />
<feMerge result="merge">
<feMergeNode in="SourceGraphic" />
<feMergeNode in="comp" />
</feMerge>
</filter>
Анимации покачивания ног требуют точного указания точки вращения, иначе нога будет летать отдельно от туловища буквы. Понадобится крайняя точка ноги.
Рассчитать координаты этой точки поможет метод JS - getBBox()
<script>
let bb = Aright.getBBox()
console.log(bb.x);
console.log(bb.y);
</script>
Для анимации покачивания туловища вместе с ножками, понадобится центр вращения группы элементов, обернутых в тег <g>
<script>
let bb = Agroup.getBBox();
console.log(bb.x + bb.width /2);
console.log(bb.y + bb.height /2);
</script>
Если есть желание разобраться, как работает приложение, прочитайте пожалуйста комментарии в коде
Чтобы выразить настроение, помогающее программированию, - выбрал трек Bobby McFerrin don't worry be happy
Update
Сделал анимацию более динамичной.
Усилил покачивание букв при движении, ускорил болтание ногами, чтобы попасть в ритм музыки, но не совсем это удалось.
Добавил анимацию закраски градиентом первой буквы
<svg id="svg1" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="100%" height="100%" viewBox="220 150 1800 993" >
<defs>
<path id="Strack" d="m322.1 281.9c0 0 75.6 16 97.2 44.3 38 49.7-13.2 140.2 30.3 185.2 32.5 33.6 94.4 9.6 137.7 27.2 26.4 10.7 44.3 38.6 71.6 46.7 7 2.1 21.8 0 21.8 0" fill="none" stroke="black" />
<path id="Ttrack" d="m 144.7062,657.68616 c 0,0 37.753,-102.00963 83.24497,-115.92056 40.72325,-12.4527 79.61954,30.26928 119.81051,44.34545 31.19139,10.92423 61.09073,30.67633 94.13684,31.11961 25.18481,0.33783 47.2325,-23.61256 72.3531,-21.78373 26.84437,1.95434 48.54017,23.28963 73.1311,34.23157 11.57342,5.1497 19.26927,7.93532 35.00956,14.78182 32.47733,14.12656 11.1456,44.34609 68.43253,-59.12726" fill="none" />
<path id="Atrack" d="m1145.2 859.2c0 0 25.7-28.7 27.2-46.7 2.4-27.5-8.5-59-28.8-77.8-31.6-29.4-89.6-10.3-124.5-35.8-36.7-26.9-35.4-92.1-75.5-113.6-74.3-39.8-252.9 0-252.9 0" fill="none" />
<path id="Ctrack" d="m220 368.1c0 0 73.9 53.2 89.1 93.5 16.5 43.6-26.4 98.1-5.5 139.7 22.7 45.3 76.6 72.2 125.4 85.8 51.6 14.4 107.1-12.2 160.6-8.8 82.6 5.3 206.4 118.7 244.3 45.1 30.3-59-143.2-138.1-143.2-138.1" fill="none" />
<path id="Ktrack" d="m1555.7 922.6c0 0 25.2-71.8 14.3-105.6-10.8-33.6-51.3-48.9-72.6-77-39.7-52.5-75.9-108.7-101.2-169.4-12.5-30.1 11.3-48.4-24.2-94.6-64.2-83.6-204.6-76.2-246.5-5.5-54.4 91.9 207.9 181.5 160.6 277.3-45.5 92.2-206 64.1-305.9 39.6C866 759.3 690.8 585.3 690.8 585.3" fill="none" />
<path id="all_Letter" transfotm="translate(0 -55)" d="M690.8 585.3H505c0 0-229.3 55.3-267.8-25.7-23.1-48.7 71.5-83.7 89.7-134.4 16-44.7-28.4-120.8 14.2-141.8 71.8-35.4 126 108.5 204 126.5 82.6 19.1 169.7-41.5 253.1-25.9 75.4 14.1 128.4 101.5 205 104.4 94.6 3.5 169.3-129.2 261.9-109.6 92.6 19.6 181 112.9 194.9 206.5 6.8 45.6-19.5 105-61.6 123.8-60.4 27-125.4-55.6-191.5-52.5-84.9 3.9-157.1 113.9-238.7 90-64.3-18.9-60.1-131.1-119.9-161.2-46.9-23.6-157.5 0-157.5 0z" style="fill:none;stroke-width:3;stroke:#0000f1"/>
<!-- Тень для букв -->
<filter id="dropShadow">
<feDropShadow dx="4" dy="4" stdDeviation="8" result="shadow"/>
<feComposite in2="mask" in="shadow" operator="in" result="comp" />
<feMerge result="merge">
<feMergeNode in="SourceGraphic" />
<feMergeNode in="comp" />
</feMerge>
</filter>
<linearGradient id="gradEarth">
<stop offset="40%" stop-color="dodgerblue"></stop>
<stop offset="100%" stop-color="yellowgreen" ></stop>
</linearGradient>
<linearGradient id="Lg" x2="0" y2="0%">
<stop offset="10%" stop-color="yellow"></stop>
<stop offset="25%" stop-color="#F437FE"></stop>
<stop offset="50%" stop-color="dodgerblue"></stop>
<stop offset="75%" stop-color="#1EFE6B"></stop>
<stop offset="100%" stop-color="red"></stop>
<animate attributeName="y2" dur="8s" values="0%;100%;100%;0%;0%" repeatCount="indefinite" />
<!-- <animate attributeName="x2" dur="6s" values="0%;100%;100%;0%;0%" repeatCount="indefinite" /> -->
</linearGradient>
</defs>
<image xlink:href="https://i.stack.imgur.com/8NIMw.jpg" width="1800px" height="993px" opacity="0.8" />
<g id="Gr_All" >
<!-- Буква S -->
<g id="Sgroup" transform="scale(1.5)" filter="url(#dropShadow)">
<!-- Левая нога -->
<path id="leftF" fill="crimson" d="m31.9 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2M58.7 64.5" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click" dur="0.5s"
values="0, 29.41,64.39;30, 29.41,64.39;0, 29.41,64.39" fill="freeze" repeatCount="indefinite" />
</path>
<!-- Правая нога -->
<path id="rightF" fill="crimson" d="m58.7 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.25s" dur="0.5s"
values="0, 56.21,64.39;30, 56.21,64.39;0, 29.41,64.39" fill="freeze" repeatCount="indefinite" />
</path>
<!-- Контур буквы S -->
<path class="Sbody" fill="url(#Lg)" d="m59.3 12.8 6.4 20.3q-5.3-4.5-9.5-6.9-4.2-2.4-6.8-2.4-2 0-3.1 1.1-1.1 1.1-1.1 3 0 2.4 2.4 4.3 2.4 1.9 7.5 3.4 11.3 3.4 15.3 6.7 4 3.3 4 8.4 0 8.3-8.1 13.6-8.1 5.3-20.9 5.3-6.5 0-12.9-2.3-6.4-2.4-11.8-6.7V69.6L13.9 49.3q4.7 3.8 9.2 5.9 4.6 2 8.3 2 4 0 6.2-1.7 2.2-1.7 2.2-4.7 0-2.8-1.8-4.5-1.8-1.7-8-3.6-9-2.9-13.5-6.8-4.4-4-4.4-9.3 0-6.7 5.9-11 5.9-4.3 15.4-4.3 6.2 0 12.2 2.2 5.9 2.2 12.2 6.9z">
<!-- Покачивание буквы S -->
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="1.5s"
values="
0, 43.25,51.14;
30, 43.25,51.14;
0, 43.25,51.14;
-30, 43.25,51.14;
0, 43.25,51.14"
fill="freeze"
repeatCount="indefinite"
additive="sum"
restart="whenNotActive" />
</path>
</g>
<animateMotion xlink:href="#Sgroup" begin="btn1.click" dur="18s" restart="whenNotActive">
<mpath xlink:href="#Strack" />
</animateMotion>
<!-- Буква T -->
<g id="Tgroup" transform="scale(1.5)" filter="url(#dropShadow)">
<path id="Tleft" fill="#F437FE" d="m90.2 66.8c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2m26.8 0.1M117 66.8" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click" dur="1s"
values="0, 87.71,66.69;30, 87.71,66.69;0, 87.71,66.69" fill="freeze" repeatCount="indefinite" />
</path>
<path id="Tright" fill="#F437FE" d="m117 66.8c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="1s"
values="0,114.51,66.69;30, 114.51,66.69;0,114.51,66.69" fill="freeze" repeatCount="indefinite" />
</path>
<path class="TBody" fill="#F437FE" d="M136.9 11.5 118.6 47.3 116.2 20 108.9 60.1 121.7 69.6 87 67.7 96.1 61 85.5 19.2 81.9 52.3 74.5 15.7Z">
<!-- Покачивание буквы T -->
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="2s"
values="
10, 105.69,52.39;
-10, 105.69,52.39;
-10, 105.69,52.39;
10, 105.69,52.39"
fill="freeze"
repeatCount="indefinite"
additive="sum"
restart="whenNotActive" />
</path>
</g>
<animateMotion xlink:href="#Tgroup" begin="btn1.click" dur="18s" restart="whenNotActive">
<mpath xlink:href="#Ttrack" />
</animateMotion>
<!-- Буква A -->
<g id="Agroup" transform="scale(1.5)" filter="url(#dropShadow)">
<path id="Aleft" fill="dodgerblue" d="m147.4 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2m26.8 0.1M183.5 64.5" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click" dur="0.5s"
values="0, 144.91,64.39;30, 144.91,64.39;0, 144.91,64.39" fill="freeze" repeatCount="indefinite" />
</path>
<path id="Aright" fill="dodgerblue" d="m183.5 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.25s" dur="0.5s"
values="0,181.01,64.39;30, 181.01,64.39;0,181.01,64.39" fill="freeze" repeatCount="indefinite" />
</path>
<path class="ABody" fill="dodgerblue" d="m171 38-5.3-15.1-5.1 18.6zm30.5 21.7-33 10.2 8.4-11.3-2.4-8.4-12.3 0.9-3.6 9.8 5.3 4.2-35.9 2.6 8.7-6.5 11.6-45.2-6.6-4.3 44.5 1.5-10.4 3.7 18.2 40.6z">
<!-- Покачивание буквы A -->
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="2s"
values="
0,164.7,51.4;
30,164.7,51.4;
0,164.7,51.4;
-30,164.7,51.4;
0,164.7,51.4"
fill="freeze"
repeatCount="indefinite"
additive="sum"
restart="whenNotActive" />
</path>
</g>
<animateMotion xlink:href="#Agroup" begin="btn1.click" dur="18s" restart="whenNotActive">
<mpath xlink:href="#Atrack" />
</animateMotion>
<!-- Буква C -->
<g id="Cgroup" transform="scale(1.5)" filter="url(#dropShadow)">
<path id="Cleft" fill="#1EFE6B" d="m212.7 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2m26.8 0.1M238.4 64.5" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click" dur="1s"
values="0,210.21,64.39;30, 210.21,64.39;0,210.21,64.39" fill="freeze" repeatCount="indefinite" />
</path>
<path id="Cright" fill="#1EFE6B" d="m238.4 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="1s"
values="0,235.91,64.39;30, 235.91,64.39;0,235.91,64.39" fill="freeze" repeatCount="indefinite" />
</path>
<path class="CBody" fill="#1EFE6B" d="m256.7 40.5-4 26.3-1.6-10.7q-5.5 6.5-12.1 9.9-6.6 3.4-13.8 3.4-4.7 0-9.2-1.9-4.5-2-8.1-5.7-4.3-4.3-6.6-9.7-2.3-5.4-2.3-11 0-7.8 3.3-14.2 3.3-6.4 9.3-10.5 4-2.7 8.7-4 4.7-1.3 10.1-1.3 5 0 9.2 1.6 4.3 1.6 8 4.7l2.2-6.3 3.9 20.7q-2.5-3.6-5.9-5.5-3.4-1.9-7.3-1.9-4.5 0-7.3 2.7-2.8 2.6-2.8 7 0 4.6 3.4 7.8 3.4 3.1 8.6 3.1 3.2 0 6.6-1.1 3.4-1.1 7.4-3.4z">
<!-- Покачивание буквы C -->
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="2s"
values="
10, 227.9,51.1;
-10,227.9,51.1;
-10, 227.9,51.1;
10, 227.9,51.1"
fill="freeze"
repeatCount="indefinite"
additive="sum"
restart="whenNotActive" />
</path>
</g>
<animateMotion xlink:href="#Cgroup" begin="btn1.click" dur="18s" restart="whenNotActive" >
<mpath xlink:href="#Ctrack" />
</animateMotion>
<!-- Буква K -->
<g id="Kgroup" transform="scale(1.5)" filter="url(#dropShadow)">
<path id="Kleft" fill="#FFDD00" d="m274.6 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2m26.8 0.1M313 64.5" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click" dur="0.5s"
values="0,272.11,64.39;30, 272.11,64.39;0,272.11,64.39" repeatCount="indefinite" />
</path>
<path id="Kright" fill="#FFDD00" d="m313 64.5c0 0-5.6 18.1 0 23.7 3.1 3.1 12.4 4.3 12.9 0 0.4-3.4-9.9-2.6-9.9-2.6l-3-21.2" class="leg">
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.25s" dur="0.5s"
values="0,310.51,64.39;30, 310.51,64.39;0,310.51,64.39" fill="freeze" repeatCount="indefinite" />
</path>
<path class="KBody" fill="#FFDD00" d="m329.1 68.8-30.1-3.7 6-3.9-17.6-18.1-2 0.4 4.1 20.1 6.4 6.8-38.1-4.3 6.8-5 3.6-41.2-7.4-4.7 30.5-3.7-8.5 6.6 2.5 16 13.4-15.9-3.7-4.6 27.7 1-19.7 15.8z">
<!-- Покачивание буквы K -->
<animateTransform attributeName="transform" type="rotate" begin="btn1.click+0.5s" dur="2s"
values="
0, 293.5,51.3;
30,293.5,51.3;
0,293.5,51.3;
-30, 293.5,51.3;
0, 293.5,51.3"
fill="freeze"
repeatCount="indefinite"
additive="sum"
restart="whenNotActive" />
</path>
</g>
<animateMotion id="an_k" xlink:href="#Kgroup" begin="btn1.click" dur="18s" restart="whenNotActive" >
<mpath xlink:href="#Ktrack" />
</animateMotion>
<!-- Анимация всех букв вместе -->
<animateMotion xlink:href="#Gr_All" begin="an_k.end" dur="18s" repeatCount="4" >
<mpath xlink:href="#all_Letter" fill="freeze" additive="sum" restart="whenNotActive" />
</animateMotion>
</g> <!-- Все буквы вместе-->
<g id="btn1" onclick='play()' >
<circle cx="736" cy="263" r="8" fill="url(#gradEarth)" filter="url(#dropShadow)" />
<text id="txt1" x="750" y="270" font-size="2rem" fill="crimson" >Start</text>
</g>
</svg>
<script>
var zodiac = new Audio();
zodiac.src = src="https://svg-art.ru/files/bobby-mcferrin-don't-worry-be-happy.mp3";
function play() {
zodiac.play();
}
</script>
Простенькая анимация, которую я нашел на CodePen и решил поделиться ею с Вами. Автор кода: клик
body{
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #000;
}
p{
position: relative;
font-family: sans-serif;
text-transform: uppercase;
font-size: 2em;
letter-spacing: 4px;
overflow: hidden;
background: linear-gradient(90deg, #000, #fff, #000);
background-repeat: no-repeat;
animation: animate 3s linear infinite;
background-size: 80%;
-webkit-background-clip: text;
-webkit-text-fill-color: rgba(255, 255, 255, 0);
}
@keyframes animate{
0%
{
background-position: -500%;
}
100%
{
background-position: 500%;
}
}
<p>StackOverflow</p>
UPD: Сделал что-то свое по примеру выше, на конкурс не претендую, но все же, прошу оценить.
body{
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background: #000;
}
p{
position: relative;
color: white;
font-family: sans-serif;
text-transform: uppercase;
font-size: 2em;
letter-spacing: 4px;
animation: animate 3s linear infinite;
background-size: 80%;
}
@keyframes animate{
50%{color: white;}
50%{color: orange; text-shadow: 0 0 5px orange, 0 0 10px orange;}
<img src="https://upload.wikimedia.org/wikipedia/commons/8/81/Stackoverflow_icon.png">
<p>StackOverflow</p>
const nearDist = 0.1;
const farDist = 30000;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75,
window.innerWidth / window.innerHeight, nearDist, farDist);
camera.position.x = farDist * -10;
camera.position.z = 500;
const renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.querySelector("#canvas-wrapper").appendChild(renderer.domElement);
const cubeSize = 200;
const radius = 4;
const detail = 2;
var rectLight = new THREE.RectAreaLight( 0xffffff50, 110, 110, 110 );
rectLight.position.set( 0, 5, 5 );
scene.add( rectLight );
const geometry = new THREE.TetrahedronBufferGeometry(radius, detail);
const material = new THREE.MeshNormalMaterial();
const group = new THREE.Group();
for (let i = 0; i < 4000; i++)
{
const mesh = new THREE.Mesh(geometry, material);
const dist = farDist / 3;
const distDouble = dist * 2;
const tau = 2 * Math.PI;
mesh.position.x = Math.random() * distDouble - dist;
mesh.position.y = Math.random() * distDouble - dist;
mesh.position.z = Math.random() * distDouble - dist;
mesh.rotation.x = Math.random() * tau;
mesh.rotation.y = Math.random() * tau;
mesh.rotation.z = Math.random() * tau;
mesh.matrixAutoUpdate = false;
mesh.updateMatrix();
group.add(mesh);
}
scene.add(group);
const loader = new THREE.FontLoader();
const textMesh = new THREE.Mesh();
const createTypo = font => {
const word = "StackOverflow";
const typoProperties = {
font: font,
size: cubeSize,
height: cubeSize / 2,
curveSegments: 20,
bevelEnabled: true,
bevelSize: 6,
bevelOffset: 1,
bevelSegments: 0 };
const text = new THREE.TextGeometry(word, typoProperties, rectLight);
textMesh.geometry = text;
textMesh.material = material;
textMesh.position.x = cubeSize * -2;
textMesh.position.z = cubeSize * -1;
scene.add(textMesh);
};
let mouseX = 0;
let mouseY = 0;
const mouseFX = {
windowHalfX: window.innerWidth / 2,
windowHalfY: window.innerHeight / 2,
coordinates: function (coordX, coordY) {
mouseX = (coordX - mouseFX.windowHalfX) * 10;
mouseY = (coordY - mouseFX.windowHalfY) * 10;
},
onMouseMove: function (e) {
mouseFX.coordinates(e.clientX, e.clientY);
},
onTouchMove: function (e) {
mouseFX.coordinates(e.changedTouches[0].clientX, e.changedTouches[0].clientY);
} };
const render = () => {
requestAnimationFrame(render);
camera.position.x += (mouseX - camera.position.x) * 0.04;
camera.position.y += (mouseY * -1 - camera.position.y) * 0.04;
camera.lookAt(scene.position);
const t = Date.now() * 0.0005;
const rx = Math.sin(t * 0.5) * 0.1;
const ry = Math.sin(t * 0.0) * 0.1;
const rz = Math.sin(t * 0.0) * 0.1;
group.rotation.x = rx;
group.rotation.y = ry;
group.rotation.z = rz;
textMesh.rotation.x = rx;
textMesh.rotation.y = ry;
textMesh.rotation.z = rx;
renderer.render(scene, camera);
};
render();
document.addEventListener("mousemove", mouseFX.onMouseMove, false);
document.addEventListener("touchmove", mouseFX.onTouchMove, false);
loader.load("https://threejs.org/examples/fonts/helvetiker_regular.typeface.json", createTypo);
body {
=
margin: 0;
padding: 0;
overflow: hidden;
}
*{
border: 0;
margin: 0;
padding: 0;
box-sizing: border-box;
}
<script src='https://cdnjs.cloudflare.com/ajax/libs/three.js/108/three.min.js'></script>
<div id="canvas-wrapper">
Не успел полностью доделать, не разобрался с подсветкой bloom Shader, хотел сделать звезды реалестичными))
body {
font-family: "Oswald", sans-serif;
color: #fff;
background: #000;
}
#wrapper {
width: 800px;
height: 300px;
text-align: center;
position: relative;
overflow: hidden;
font-weight: 400;
text-transform: uppercase;
}
#t1 {
width: 100%;
font-size: 40px;
letter-spacing: 15px;
animation: t1 5000ms infinite alternate;
}
#t2 {
width: 200%;
position: absolute;
top: 0;
font-size: 80px;
filter: blur(5px);
letter-spacing: 45px;
left: -50%;
animation: t1 5000ms infinite alternate;
}
@keyframes t1 {
0% {
transform: translateX(-4%);
}
20% {
transform: translateX(-1%);
}
21.6% {
text-shadow: none;
font-weight: 400;
}
22% {
text-shadow: 0 0 14px #fff;
font-weight: 900;
}
22.4% {
text-shadow: none;
font-weight: 400;
}
30% {
transform: translateX(-2%);
}
40% {
transform: translateX(0%);
}
50% {
transform: translateX(2%);
}
60% {
transform: translateX(1%);
}
61.6% {
text-shadow: none;
font-weight: 400;
}
62% {
text-shadow: 0 0 14px #fff;
font-weight: 900;
}
62.4% {
text-shadow: none;
font-weight: 400;
}
70% {
transform: translateX(2%);
}
80% {
transform: translateX(-1%);
}
84.6% {
text-shadow: none;
font-weight: 400;
}
85% {
text-shadow: 0 0 14px #fff;
font-weight: 900;
}
85.4% {
text-shadow: none;
font-weight: 400;
}
90% {
transform: translateX(1%);
}
100% {
transform: translateX(4%);
}
}
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@400;700&display=swap" rel="stylesheet">
<div id="wrapper">
<div id="t1">Stackoverflow</div>
<div id="t2">Stackoverflow</div>
</div>
Хоть и поздно, не совсем по правилам и не так красиво, но сам недавно столкнулся с необходимостью анимировать градиент текста. Да, моё решение весьма затратно по ресурсам, не такое оригинальное, как у остальных, но очень часто нужно в реальных проектах.
Нужно было сделать просто вращение градиента с плавным переливом цветов с разной насыщенностью и яркостью. Очевидно, что в данном случае было бы логично использовать HSL. По синусоиде менять значения, только чтобы синусоиды были немного разные. В общем, вот, реализация.
Живые примеры доступны на hate-m.tk и imgay.design (просто демонстрационные страницы, переходить не рекомендую и не призываю)
Код не идеален, я не фронтендер, один из первых опытов работы с JS
let deg = Math.random() * 360;
document.documentElement.style.setProperty('--deg', `${deg}deg`);
let hues = [[145, 195], [230, 240], [320, 350], [45, 65], [75, 150], [250, 290]];
let hue1 = hues[Math.floor(Math.random() * hues.length)];
let hue2 = hues[Math.floor(Math.random() * hues.length)];
while (hue1 === hue2) {
hue2 = hues[Math.floor(Math.random() * hues.length)];
}
function degrot() {
function rot(variable, ary) {
let nv = ary.shift();
ary.push(nv);
let pf = "%";
if (variable.indexOf("--hue") + 1) {
pf = "";
}
document.documentElement.style.setProperty(variable, `${nv}${pf}`);
return ary
}
hues1 = rot('--hue1', hues1);
hues2 = rot('--hue2', hues2);
sats1 = rot('--sat1', sats1);
sats2 = rot('--sat2', sats2);
ligs1 = rot('--lig1', ligs1);
ligs2 = rot('--lig2', ligs2);
}
let hues1 = Array.from(Array(121).keys(), x => (Math.cos(x / 120 * 2 * Math.PI) + 1) / 2 * (hue1[1] - hue1[0]) + hue1[0]);
let hues2 = Array.from(Array(91).keys(), x => (Math.cos(x / 90 * 2 * Math.PI) + 1) / 2 * (hue2[1] - hue2[0]) + hue2[0])
let sats1 = Array.from(Array(61).keys(), x => (Math.cos(x / 60 * 2 * Math.PI + 1.4) + 1) / 2 * (100 - 65) + 65);
let sats2 = Array.from(Array(91).keys(), x => (Math.cos(x / 90 * 2 * Math.PI) + 1) / 2 * (100 - 65) + 65);
let ligs1 = Array.from(Array(151).keys(), x => (Math.cos(x / 150 * 2 * Math.PI + 1.9) + 1) / 2 * (85 - 60) + 60);
let ligs2 = Array.from(Array(81).keys(), x => (Math.cos(x / 80 * 2 * Math.PI) + 1) / 2 * (85 - 60) + 60);
setInterval('deg = deg + (Math.random());document.documentElement.style.setProperty("--deg", `${deg%360}deg`);', 20);
setInterval(degrot, 66);
#body {
background-color: black;
padding: 2em;
}
#text {
font-weight: 800;
background: linear-gradient(var(--deg, 45deg), hsl(var(--hue1), var(--sat1), var(--lig1)) 0%, hsl(var(--hue2), var(--sat2), var(--lig2)) 100%);
display: inline;
font-size: 3em;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
<!--<script src="https://imgay.design/imgay.js"></script>-->
<div id="body"><h1 id="text">StackOverflow</h1></div>
