Última actividad 3 weeks ago

book_anim.html Sin formato
1<!DOCTYPE html>
2<html lang="ru">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Книга - SVG Анимация</title>
7 <style>
8 body {
9 margin: 0;
10 display: flex;
11 justify-content: center;
12 align-items: center;
13 min-height: 100vh;
14 background: #fff;
15 }
16 svg {
17 border-radius: 25px;
18 }
19 </style>
20</head>
21<body>
22<svg id="bookSvg" width="400" height="400" viewBox="0 0 400 400"></svg>
23
24<script>
25 const svg = document.getElementById('bookSvg');
26 const NS = 'http://www.w3.org/2000/svg';
27
28 const svgWidth = 400;
29 const svgHeight = 400;
30
31 // Страницы
32 const lineCount = 5;
33 const lineDelay = 16;
34
35 // Книга по центру
36 const bookWidth = 240;
37 const bookHeight = 360;
38 const bookX = (svgWidth - bookWidth) / 2;
39 const bookY = (svgHeight - bookHeight) / 2;
40
41 // Параметры полос (страниц)
42 const desiredLineSpacing = 12;
43 const baseLineWidth = 2;
44 const maxLineWidth = 14;
45 const maxLineHeight = bookHeight - 36;
46 const innerPaddingX = 14;
47
48 // Задержка между появлением/исчезновением полос
49 const appearStagger = 8;
50
51 // Вычисляем реальное расстояние между полосами
52 let lineSpacing;
53 if (lineCount > 1) {
54 const maxSpan = Math.max(0, bookWidth - maxLineWidth - 2 * innerPaddingX);
55 const wishSpan = desiredLineSpacing * (lineCount - 1);
56 const realSpan = Math.min(wishSpan, maxSpan);
57 lineSpacing = realSpan / (lineCount - 1);
58 } else {
59 lineSpacing = 0;
60 }
61 const linesSpan = lineSpacing * (lineCount - 1);
62
63 // Правая группа (стартовая позиция)
64 const rightBase = bookX + bookWidth - innerPaddingX - maxLineWidth;
65 const lineStartX = rightBase - linesSpan;
66
67 // Левая группа (финишная позиция)
68 const leftLimit = bookX + innerPaddingX;
69
70 // Состояние анимации
71 let phase = 0;
72 let time = 0;
73
74 // Тайминги (в кадрах)
75 const baseAppearDuration = 40;
76 const appearDuration = baseAppearDuration + (lineCount - 1) * appearStagger;
77
78 const baseFlipDuration = 120;
79 const flipDuration = baseFlipDuration + (lineCount - 1) * lineDelay;
80
81 const baseDisappearDuration = 40;
82 const disappearDuration = baseDisappearDuration + (lineCount - 1) * appearStagger;
83
84 const pauseDuration = 30;
85
86 // Книга
87 const book = document.createElementNS(NS, 'rect');
88 book.setAttribute('x', bookX);
89 book.setAttribute('y', bookY);
90 book.setAttribute('width', bookWidth);
91 book.setAttribute('height', bookHeight);
92 book.setAttribute('fill', '#000');
93 book.setAttribute('rx', '4');
94 svg.appendChild(book);
95
96 // Полосы (страницы)
97 const lines = [];
98 for (let i = 0; i < lineCount; i++) {
99 const line = document.createElementNS(NS, 'rect');
100 line.setAttribute('fill', '#fff');
101 line.setAttribute('rx', '1');
102 svg.appendChild(line);
103
104 const baseX = lineStartX + i * lineSpacing;
105 const targetX = leftLimit + i * lineSpacing;
106 const moveDistance = baseX - targetX;
107
108 lines.push({
109 el: line,
110 baseX,
111 targetX,
112 moveDistance,
113 currentX: baseX,
114 width: baseLineWidth,
115 height: 0
116 });
117 }
118
119 // Функции плавности
120 function easeInOutQuad(t) {
121 return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
122 }
123 function easeOutQuad(t) {
124 return 1 - (1 - t) * (1 - t);
125 }
126 function easeInQuad(t) {
127 return t * t;
128 }
129
130 // Обновление одной полосы
131 function updateLine(line) {
132 const el = line.el;
133 const centerY = bookY + bookHeight / 2;
134
135 el.setAttribute('x', line.currentX);
136 el.setAttribute('y', centerY - line.height / 2);
137 el.setAttribute('width', line.width);
138 el.setAttribute('height', Math.max(0, line.height));
139 }
140
141 // Главный цикл анимации
142 function animate() {
143 time++;
144
145 if (phase === 0) {
146 // Появление: слева направо (индекс 0 первый, потом 1, 2...)
147 for (let i = 0; i < lineCount; i++) {
148 // Задержка увеличивается с индексом → левые появляются раньше
149 const delay = (lineCount - 1 - i) * appearStagger;
150 const localTime = Math.max(0, time - delay);
151 const progress = Math.min(1, localTime / baseAppearDuration);
152 const easedProgress = easeOutQuad(progress);
153
154 lines[i].height = maxLineHeight * easedProgress;
155 lines[i].currentX = lines[i].baseX;
156 lines[i].width = baseLineWidth;
157 updateLine(lines[i]);
158 }
159
160 if (time >= appearDuration) {
161 phase = 1;
162 time = 0;
163 }
164 } else if (phase === 1) {
165 // Перелистывание
166 for (let i = 0; i < lineCount; i++) {
167 const delay = i * lineDelay;
168 const localTime = Math.max(0, time - delay);
169 const progress = Math.min(1, localTime / baseFlipDuration);
170
171 const moveProgress = easeInOutQuad(progress);
172 lines[i].currentX = lines[i].baseX - lines[i].moveDistance * moveProgress;
173
174 const widthProgress = progress < 0.5
175 ? easeOutQuad(progress * 2)
176 : 1 - easeInQuad((progress - 0.5) * 2);
177
178 lines[i].width = baseLineWidth + (maxLineWidth - baseLineWidth) * widthProgress;
179
180 updateLine(lines[i]);
181 }
182
183 if (time >= flipDuration) {
184 phase = 2;
185 time = 0;
186 }
187 } else if (phase === 2) {
188 // Исчезновение: справа налево (последний индекс первый, потом предпоследний...)
189 for (let i = 0; i < lineCount; i++) {
190 // Задержка уменьшается с индексом → правые (большие индексы) исчезают раньше
191 const delay = (lineCount - 1 - i) * appearStagger;
192 const localTime = Math.max(0, time - delay);
193 const progress = Math.min(1, localTime / baseDisappearDuration);
194 const easedProgress = easeInQuad(progress);
195
196 lines[i].height = maxLineHeight * (1 - easedProgress);
197 updateLine(lines[i]);
198 }
199
200 if (time >= disappearDuration + pauseDuration) {
201 // Сброс
202 phase = 0;
203 time = 0;
204 for (let i = 0; i < lineCount; i++) {
205 lines[i].currentX = lines[i].baseX;
206 lines[i].width = baseLineWidth;
207 lines[i].height = 0;
208 }
209 }
210 }
211
212 requestAnimationFrame(animate);
213 }
214
215 animate();
216</script>
217</body>
218</html>