@hortonstudio/main 1.4.3 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/animations/text.js +132 -101
- package/index.js +1 -1
- package/package.json +1 -1
package/animations/text.js
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
const API_NAME = "hsmain";
|
2
2
|
|
3
3
|
// Module-scoped variables for resize handling
|
4
|
-
let resizeTimeout;
|
5
4
|
let resizeHandler;
|
6
5
|
|
7
6
|
// Check for reduced motion preference
|
@@ -107,36 +106,40 @@ async function waitForFonts() {
|
|
107
106
|
}
|
108
107
|
|
109
108
|
function findTextElement(container) {
|
110
|
-
//
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
109
|
+
// Safari-optimized: Use simple querySelector instead of TreeWalker
|
110
|
+
// TreeWalker is notoriously slow in Safari/WebKit
|
111
|
+
const directTextElement = container.querySelector(':scope > *:first-child');
|
112
|
+
if (directTextElement && directTextElement.textContent.trim()) {
|
113
|
+
return directTextElement;
|
114
|
+
}
|
115
|
+
|
116
|
+
// Fallback: check if container itself has text
|
117
|
+
if (container.textContent.trim()) {
|
118
|
+
return container;
|
119
|
+
}
|
120
|
+
|
121
|
+
// Last resort: find any element with text content
|
122
|
+
const textElements = container.querySelectorAll('*');
|
123
|
+
for (let i = 0; i < textElements.length; i++) {
|
124
|
+
const el = textElements[i];
|
125
|
+
if (el.textContent.trim() && !el.querySelector('*')) {
|
126
|
+
return el;
|
122
127
|
}
|
123
|
-
|
128
|
+
}
|
124
129
|
|
125
|
-
return
|
130
|
+
return container;
|
126
131
|
}
|
127
132
|
|
128
133
|
function showElementsWithoutAnimation() {
|
129
|
-
//
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
allTextElements.forEach((element) => {
|
134
|
+
// Safari-optimized: Use single query with comma-separated selectors
|
135
|
+
// This is significantly faster than multiple querySelectorAll calls
|
136
|
+
const allTextElements = document.querySelectorAll(
|
137
|
+
'[data-hs-anim="char"], [data-hs-anim="word"], [data-hs-anim="line"], [data-hs-anim="appear"], [data-hs-anim="reveal"], [data-hs-anim="group"]'
|
138
|
+
);
|
139
|
+
|
140
|
+
// Batch DOM operations for better Safari performance
|
141
|
+
const elementsArray = Array.from(allTextElements);
|
142
|
+
elementsArray.forEach((element) => {
|
140
143
|
try {
|
141
144
|
gsap.set(element, {
|
142
145
|
autoAlpha: 1,
|
@@ -160,30 +163,56 @@ const CharSplitAnimations = {
|
|
160
163
|
|
161
164
|
const elements = document.querySelectorAll('[data-hs-anim="char"]');
|
162
165
|
|
163
|
-
elements
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
166
|
+
// Safari optimization: Process elements in smaller batches to avoid blocking
|
167
|
+
const processInBatches = (elements, batchSize = 5) => {
|
168
|
+
return new Promise((resolve) => {
|
169
|
+
let index = 0;
|
170
|
+
|
171
|
+
const processBatch = () => {
|
172
|
+
const endIndex = Math.min(index + batchSize, elements.length);
|
173
|
+
|
174
|
+
for (let i = index; i < endIndex; i++) {
|
175
|
+
const container = elements[i];
|
176
|
+
|
177
|
+
// Skip if already processed
|
178
|
+
if (container.hasAttribute('data-split-processed')) continue;
|
179
|
+
|
180
|
+
const textElement = findTextElement(container);
|
181
|
+
if (!textElement) continue;
|
182
|
+
|
183
|
+
try {
|
184
|
+
const split = SplitText.create(textElement, {
|
185
|
+
type: "words,chars",
|
186
|
+
mask: "chars",
|
187
|
+
charsClass: "char",
|
188
|
+
});
|
189
|
+
container.splitTextInstance = split;
|
190
|
+
container.setAttribute('data-split-processed', 'true');
|
191
|
+
|
192
|
+
gsap.set(split.chars, {
|
193
|
+
yPercent: config.charSplit.yPercent,
|
194
|
+
});
|
195
|
+
gsap.set(textElement, { autoAlpha: 1 });
|
196
|
+
} catch (error) {
|
197
|
+
console.warn("Error creating char split:", error);
|
198
|
+
gsap.set(textElement, { autoAlpha: 1 });
|
199
|
+
}
|
200
|
+
}
|
201
|
+
|
202
|
+
index = endIndex;
|
203
|
+
|
204
|
+
if (index < elements.length) {
|
205
|
+
requestAnimationFrame(processBatch);
|
206
|
+
} else {
|
207
|
+
resolve();
|
208
|
+
}
|
209
|
+
};
|
210
|
+
|
211
|
+
processBatch();
|
212
|
+
});
|
213
|
+
};
|
177
214
|
|
178
|
-
|
179
|
-
yPercent: config.charSplit.yPercent,
|
180
|
-
});
|
181
|
-
gsap.set(textElement, { autoAlpha: 1 });
|
182
|
-
} catch (error) {
|
183
|
-
console.warn("Error creating char split:", error);
|
184
|
-
gsap.set(textElement, { autoAlpha: 1 });
|
185
|
-
}
|
186
|
-
});
|
215
|
+
await processInBatches(elements);
|
187
216
|
},
|
188
217
|
|
189
218
|
async animate() {
|
@@ -193,42 +222,49 @@ const CharSplitAnimations = {
|
|
193
222
|
return;
|
194
223
|
}
|
195
224
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
225
|
+
// Safari optimization: Cache elements and reduce DOM queries
|
226
|
+
const elements = document.querySelectorAll('[data-hs-anim="char"]');
|
227
|
+
|
228
|
+
elements.forEach((container) => {
|
229
|
+
const textElement = findTextElement(container);
|
230
|
+
if (!textElement) return;
|
231
|
+
|
232
|
+
try {
|
233
|
+
const chars = container.querySelectorAll(".char");
|
234
|
+
if (chars.length === 0) return;
|
235
|
+
|
236
|
+
const tl = gsap.timeline({
|
237
|
+
scrollTrigger: {
|
238
|
+
trigger: container,
|
239
|
+
start: config.charSplit.start,
|
240
|
+
invalidateOnRefresh: true,
|
241
|
+
toggleActions: "play none none none",
|
242
|
+
once: true,
|
243
|
+
},
|
244
|
+
onComplete: () => {
|
245
|
+
// Use requestAnimationFrame for cleanup to avoid Safari blocking
|
246
|
+
requestAnimationFrame(() => {
|
212
247
|
if (container.splitTextInstance) {
|
213
248
|
container.splitTextInstance.revert();
|
214
249
|
delete container.splitTextInstance;
|
215
250
|
container.removeAttribute('data-split-processed');
|
216
251
|
}
|
217
|
-
}
|
218
|
-
}
|
252
|
+
});
|
253
|
+
},
|
254
|
+
});
|
219
255
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
256
|
+
tl.to(chars, {
|
257
|
+
yPercent: 0,
|
258
|
+
duration: config.charSplit.duration,
|
259
|
+
stagger: config.charSplit.stagger,
|
260
|
+
ease: config.charSplit.ease,
|
261
|
+
});
|
226
262
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
263
|
+
activeAnimations.push({ timeline: tl, element: textElement });
|
264
|
+
} catch (error) {
|
265
|
+
console.warn("Error animating char split:", error);
|
266
|
+
}
|
267
|
+
});
|
232
268
|
},
|
233
269
|
};
|
234
270
|
|
@@ -624,39 +660,40 @@ export async function init() {
|
|
624
660
|
initAnimations();
|
625
661
|
}
|
626
662
|
|
627
|
-
//
|
663
|
+
// Safari optimization: Throttled resize handler using requestAnimationFrame
|
664
|
+
let resizeScheduled = false;
|
628
665
|
let lastWidth = window.innerWidth;
|
666
|
+
|
629
667
|
resizeHandler = () => {
|
630
668
|
const currentWidth = window.innerWidth;
|
631
|
-
if (currentWidth !== lastWidth) {
|
669
|
+
if (currentWidth !== lastWidth && !resizeScheduled) {
|
632
670
|
lastWidth = currentWidth;
|
633
|
-
|
634
|
-
|
671
|
+
resizeScheduled = true;
|
672
|
+
|
673
|
+
// Use requestAnimationFrame instead of setTimeout for better Safari performance
|
674
|
+
requestAnimationFrame(() => {
|
635
675
|
try {
|
636
676
|
ScrollTrigger.refresh();
|
637
677
|
} catch (error) {
|
638
678
|
console.warn("Error refreshing ScrollTrigger:", error);
|
679
|
+
} finally {
|
680
|
+
resizeScheduled = false;
|
639
681
|
}
|
640
|
-
}
|
682
|
+
});
|
641
683
|
}
|
642
684
|
};
|
643
|
-
window.addEventListener("resize", resizeHandler);
|
685
|
+
window.addEventListener("resize", resizeHandler, { passive: true });
|
644
686
|
|
645
|
-
//
|
646
|
-
|
647
|
-
|
687
|
+
// Safari optimization: Simplified page load handling
|
688
|
+
// Complex event handlers and multiple timeouts cause Safari lag
|
689
|
+
if (document.readyState === 'complete') {
|
690
|
+
requestAnimationFrame(() => {
|
648
691
|
try {
|
649
692
|
ScrollTrigger.refresh();
|
650
693
|
} catch (error) {
|
651
|
-
console.warn("Error refreshing ScrollTrigger
|
694
|
+
console.warn("Error refreshing ScrollTrigger:", error);
|
652
695
|
}
|
653
|
-
}
|
654
|
-
};
|
655
|
-
|
656
|
-
if (document.readyState === 'complete') {
|
657
|
-
handlePageLoad();
|
658
|
-
} else {
|
659
|
-
window.addEventListener('load', handlePageLoad);
|
696
|
+
});
|
660
697
|
}
|
661
698
|
|
662
699
|
// Initialize API with proper checks
|
@@ -677,7 +714,6 @@ export async function init() {
|
|
677
714
|
cleanup: () => {
|
678
715
|
killTextAnimations();
|
679
716
|
window.removeEventListener("resize", resizeHandler);
|
680
|
-
clearTimeout(resizeTimeout);
|
681
717
|
},
|
682
718
|
};
|
683
719
|
|
@@ -691,11 +727,6 @@ export async function init() {
|
|
691
727
|
window.removeEventListener("resize", resizeHandler);
|
692
728
|
}
|
693
729
|
|
694
|
-
// Clear timeout
|
695
|
-
if (resizeTimeout) {
|
696
|
-
clearTimeout(resizeTimeout);
|
697
|
-
}
|
698
|
-
|
699
730
|
// Remove initialized markers
|
700
731
|
document.querySelectorAll("[data-initialized]").forEach((el) => {
|
701
732
|
el.removeAttribute("data-initialized");
|
package/index.js
CHANGED