Last active 3 weeks ago

wowlikon's Avatar wowlikon revised this gist 3 weeks ago. Go to revision

1 file changed, 4 insertions, 2 deletions

book_anim.html

@@ -13,6 +13,9 @@
13 13 min-height: 100vh;
14 14 background: #fff;
15 15 }
16 + svg {
17 + border-radius: 25px;
18 + }
16 19 </style>
17 20 </head>
18 21 <body>
@@ -25,10 +28,9 @@
25 28 const svgWidth = 400;
26 29 const svgHeight = 400;
27 30
28 - // === НАСТРОЙКИ ===
31 + // Страницы
29 32 const lineCount = 5;
30 33 const lineDelay = 16;
31 - // =================
32 34
33 35 // Книга по центру
34 36 const bookWidth = 240;

wowlikon's Avatar wowlikon revised this gist 3 weeks ago. Go to revision

No changes

wowlikon's Avatar wowlikon revised this gist 3 weeks ago. Go to revision

No changes

wowlikon's Avatar wowlikon revised this gist 3 weeks ago. Go to revision

1 file changed, 216 insertions

book_anim.html(file created)

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