Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2f3899dc2 |
@@ -6,19 +6,26 @@ type Transition = (
|
|||||||
tl: gsap.core.Timeline,
|
tl: gsap.core.Timeline,
|
||||||
prev: HTMLElement,
|
prev: HTMLElement,
|
||||||
next: HTMLElement,
|
next: HTMLElement,
|
||||||
|
touch: boolean,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Rolagem como um vídeo: o Lenis suaviza a roda do mouse e o GSAP liga cada passagem de seção à posição
|
* Rolagem como um vídeo: o Lenis suaviza a roda do mouse e o GSAP liga cada passagem de seção à posição
|
||||||
* da rolagem (rolar para cima volta o "vídeo").
|
* da rolagem (rolar para cima volta o "vídeo").
|
||||||
* Truque de todas as transições: durante a última altura de tela entre duas seções, a que sai é empurrada
|
*
|
||||||
* para baixo na mesma medida da rolagem (parece parada) e a que entra pode fazer o mesmo. Assim nada
|
* Quem segura as seções paradas durante uma passagem:
|
||||||
* "rola"; só a transição acontece. Sem pin e sem mudar o layout: links internos e medições seguem iguais.
|
* - a que SAI fica com position: sticky (topo = altura da tela − altura da seção): ela para quando o fim
|
||||||
|
* dela chega ao fim da tela. Quem mantém isso é o compositor do navegador, na mesma batida da rolagem;
|
||||||
|
* - a que ENTRA só fica parada no topo no desktop (deslocamento pelo GSAP). Com a roda do mouse, o Lenis
|
||||||
|
* rola e desloca no mesmo quadro. No toque, a rolagem nativa corre em outra thread e o deslocamento
|
||||||
|
* chegava um quadro atrasado: em rolagens rápidas a seção tremia. Por isso, no toque, ela sobe com o
|
||||||
|
* dedo e a passagem usa só efeitos que não disputam a posição (recorte, transparência, giro, escala).
|
||||||
*
|
*
|
||||||
* Regras que mantêm tudo estável:
|
* Regras que mantêm tudo estável:
|
||||||
* - cada tween termina até o tempo 1 da timeline (as "travas" de posição duram exatamente 1);
|
* - cada tween termina até o tempo 1 da timeline (a trava de posição dura exatamente 1);
|
||||||
* - nenhum ancestral de um <canvas> recebe scale (o R3F mede o canvas com getBoundingClientRect);
|
* - nenhum ancestral de um <canvas> recebe scale (o R3F mede o canvas com getBoundingClientRect);
|
||||||
* - a seção que saiu fica invisível depois da passagem, para não aparecer atrás de transparências.
|
* - cada cena só fica visível da passagem em que entra até a passagem em que sai (com folga), para não
|
||||||
|
* aparecer atrás de transparências e para o navegador não pintar o que está coberto.
|
||||||
* Os valores são funções para serem recalculados quando a janela muda de tamanho (invalidateOnRefresh).
|
* Os valores são funções para serem recalculados quando a janela muda de tamanho (invalidateOnRefresh).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -28,7 +35,7 @@ const visibleCenter = (el: HTMLElement) => () =>
|
|||||||
`50% ${el.offsetHeight - vh() / 2}px`;
|
`50% ${el.offsetHeight - vh() / 2}px`;
|
||||||
|
|
||||||
/** Topo do elemento na página pelo layout (offsetTop ignora os transforms das transições, getBoundingClientRect não). */
|
/** Topo do elemento na página pelo layout (offsetTop ignora os transforms das transições, getBoundingClientRect não). */
|
||||||
export function pageTop(el: HTMLElement) {
|
function pageTop(el: HTMLElement) {
|
||||||
let top = 0;
|
let top = 0;
|
||||||
for (
|
for (
|
||||||
let node: HTMLElement | null = el;
|
let node: HTMLElement | null = el;
|
||||||
@@ -39,60 +46,66 @@ export function pageTop(el: HTMLElement) {
|
|||||||
return top;
|
return top;
|
||||||
}
|
}
|
||||||
|
|
||||||
// a seção que sai fica parada na tela enquanto a próxima entra
|
// a seção que entra fica parada no topo (só desktop, ver o comentário acima)
|
||||||
const holdPrev = (tl: gsap.core.Timeline, prev: HTMLElement) =>
|
|
||||||
tl.fromTo(prev, { y: 0 }, { y: vh }, 0);
|
|
||||||
// a seção que entra também fica parada no topo: a transição acontece "no lugar"
|
|
||||||
const holdNext = (tl: gsap.core.Timeline, next: HTMLElement) =>
|
const holdNext = (tl: gsap.core.Timeline, next: HTMLElement) =>
|
||||||
tl.fromTo(next, { y: () => -vh() }, { y: 0 }, 0);
|
tl.fromTo(next, { y: () => -vh() }, { y: 0 }, 0);
|
||||||
|
|
||||||
// uma linguagem diferente por passagem, na ordem das seções da home
|
// uma linguagem diferente por passagem, na ordem das seções da home
|
||||||
const TRANSITIONS: Transition[] = [
|
const TRANSITIONS: Transition[] = [
|
||||||
// hero → projetos: o mergulho termina no escuro, o hero some e os projetos abrem como tela de cinema
|
// hero → projetos: o mergulho termina no escuro, o hero some e os projetos abrem como tela de cinema
|
||||||
(tl, prev, next) => {
|
(tl, prev, next, touch) => {
|
||||||
holdPrev(tl, prev);
|
tl.fromTo(prev, { autoAlpha: 1 }, { autoAlpha: 0, duration: 0.4 }, 0);
|
||||||
|
if (touch) {
|
||||||
|
tl.fromTo(
|
||||||
|
next,
|
||||||
|
{
|
||||||
|
opacity: 0.3,
|
||||||
|
clipPath: "inset(0% 5% 0% 5% round 28px 28px 0px 0px)",
|
||||||
|
},
|
||||||
|
{ opacity: 1, clipPath: "inset(0% 0% 0% 0% round 0px 0px 0px 0px)" },
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
holdNext(tl, next);
|
holdNext(tl, next);
|
||||||
const band = (open: number) => () =>
|
const band = (open: number) => () =>
|
||||||
`inset(${(vh() * (1 - open)) / 2}px 0px ${next.offsetHeight - vh() + (vh() * (1 - open)) / 2}px 0px)`;
|
`inset(${(vh() * (1 - open)) / 2}px 0px ${next.offsetHeight - vh() + (vh() * (1 - open)) / 2}px 0px)`;
|
||||||
tl.fromTo(
|
tl.fromTo(
|
||||||
prev,
|
|
||||||
{ autoAlpha: 1 },
|
|
||||||
{ autoAlpha: 0, duration: 0.4 },
|
|
||||||
0,
|
|
||||||
).fromTo(
|
|
||||||
next,
|
next,
|
||||||
{ opacity: 0, clipPath: band(0.36) },
|
{ opacity: 0, clipPath: band(0.36) },
|
||||||
{ opacity: 1, clipPath: band(1), duration: 0.65 },
|
{ opacity: 1, clipPath: band(1), duration: 0.65 },
|
||||||
0.35,
|
0.35,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
// projetos → modalidades: uma íris abre do centro enquanto a anterior avança contra a câmera
|
// projetos → modalidades: uma íris se abre enquanto a anterior avança contra a câmera
|
||||||
(tl, prev, next) => {
|
(tl, prev, next, touch) => {
|
||||||
holdPrev(tl, prev);
|
|
||||||
holdNext(tl, next);
|
|
||||||
const center = () => `at 50% ${vh() / 2}px`;
|
|
||||||
tl.fromTo(
|
tl.fromTo(
|
||||||
prev,
|
prev,
|
||||||
{ scale: 1, opacity: 1, transformOrigin: visibleCenter(prev) },
|
{ scale: 1, opacity: 1, transformOrigin: visibleCenter(prev) },
|
||||||
{ scale: 1.18, opacity: 0.2 },
|
{ scale: 1.18, opacity: 0.2 },
|
||||||
0,
|
0,
|
||||||
)
|
);
|
||||||
|
// no toque a seção sobe com o dedo: a íris nasce do topo dela, como uma bolha subindo
|
||||||
|
const center = () => (touch ? "at 50% 0px" : `at 50% ${vh() / 2}px`);
|
||||||
|
const radius = () =>
|
||||||
|
touch
|
||||||
|
? Math.hypot(window.innerWidth / 2, vh())
|
||||||
|
: Math.hypot(window.innerWidth / 2, vh() / 2);
|
||||||
|
if (!touch) holdNext(tl, next);
|
||||||
// power1.in: o círculo cresce devagar no começo, sem tomar a tela antes do fim da passagem
|
// power1.in: o círculo cresce devagar no começo, sem tomar a tela antes do fim da passagem
|
||||||
.fromTo(
|
tl.fromTo(
|
||||||
next,
|
next,
|
||||||
{ clipPath: () => `circle(0px ${center()})` },
|
{ clipPath: () => `circle(0px ${center()})` },
|
||||||
{
|
{
|
||||||
clipPath: () =>
|
clipPath: () => `circle(${radius()}px ${center()})`,
|
||||||
`circle(${Math.hypot(window.innerWidth / 2, vh() / 2)}px ${center()})`,
|
|
||||||
ease: "power1.in",
|
ease: "power1.in",
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
// modalidades → IA: corte lateral, uma tela empurra a outra
|
// modalidades → IA: corte lateral, uma tela empurra a outra
|
||||||
(tl, prev, next) => {
|
(tl, prev, next, touch) => {
|
||||||
holdPrev(tl, prev);
|
if (!touch) holdNext(tl, next);
|
||||||
holdNext(tl, next);
|
|
||||||
tl.fromTo(
|
tl.fromTo(
|
||||||
prev,
|
prev,
|
||||||
{ xPercent: 0, opacity: 1 },
|
{ xPercent: 0, opacity: 1 },
|
||||||
@@ -100,23 +113,24 @@ const TRANSITIONS: Transition[] = [
|
|||||||
0,
|
0,
|
||||||
).fromTo(next, { xPercent: 100 }, { xPercent: 0, ease: "power2.out" }, 0);
|
).fromTo(next, { xPercent: 100 }, { xPercent: 0, ease: "power2.out" }, 0);
|
||||||
},
|
},
|
||||||
// IA → qualidade: a seção sobe mais rápido que a rolagem, como um cartão, sobre a anterior que escurece.
|
// IA → qualidade: a seção sobe como um cartão sobre a anterior, que escurece.
|
||||||
// Só translate/opacity: recorte animado, escala (o Chrome rasteriza de novo a cada novo tamanho) e o
|
// Só translate/opacity: recorte animado, escala (o Chrome rasteriza de novo a cada novo tamanho) e o
|
||||||
// desenho do diagrama a cada quadro dobravam o custo de pintura desta passagem. O diagrama se desenha
|
// desenho do diagrama a cada quadro dobravam o custo de pintura desta passagem. O diagrama se desenha
|
||||||
// uma vez, em CSS, quando a passagem começa (classe is-drawing, ver onEnter).
|
// uma vez, em CSS, quando a passagem começa (classe is-drawing, ver onEnter).
|
||||||
(tl, prev, next) => {
|
(tl, prev, next, touch) => {
|
||||||
holdPrev(tl, prev);
|
tl.fromTo(prev, { opacity: 1 }, { opacity: 0.2 }, 0);
|
||||||
tl.fromTo(prev, { opacity: 1 }, { opacity: 0.2 }, 0).fromTo(
|
// no desktop ela sobe um pouco mais rápido que a rolagem; no toque, só com o dedo
|
||||||
|
if (!touch)
|
||||||
|
tl.fromTo(
|
||||||
next,
|
next,
|
||||||
{ yPercent: 0, y: () => vh() * 0.35 },
|
{ y: () => vh() * 0.35 },
|
||||||
{ y: 0, ease: "power2.out" },
|
{ y: 0, ease: "power2.out" },
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
// qualidade → sobre: a página entra girando pela esquerda, como uma folha virando (só transform)
|
// qualidade → sobre: a página entra girando pela esquerda, como uma folha virando (só transform)
|
||||||
(tl, prev, next) => {
|
(tl, prev, next, touch) => {
|
||||||
holdPrev(tl, prev);
|
if (!touch) holdNext(tl, next);
|
||||||
holdNext(tl, next);
|
|
||||||
tl.fromTo(
|
tl.fromTo(
|
||||||
prev,
|
prev,
|
||||||
{ opacity: 1, xPercent: 0 },
|
{ opacity: 1, xPercent: 0 },
|
||||||
@@ -130,9 +144,8 @@ const TRANSITIONS: Transition[] = [
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
// sobre → contato: o contato cresce do centro, como um zoom de câmera
|
// sobre → contato: o contato cresce do centro, como um zoom de câmera
|
||||||
(tl, prev, next) => {
|
(tl, prev, next, touch) => {
|
||||||
holdPrev(tl, prev);
|
if (!touch) holdNext(tl, next);
|
||||||
holdNext(tl, next);
|
|
||||||
tl.fromTo(
|
tl.fromTo(
|
||||||
prev,
|
prev,
|
||||||
{ scale: 1, opacity: 1, transformOrigin: visibleCenter(prev) },
|
{ scale: 1, opacity: 1, transformOrigin: visibleCenter(prev) },
|
||||||
@@ -142,9 +155,9 @@ const TRANSITIONS: Transition[] = [
|
|||||||
.fromTo(
|
.fromTo(
|
||||||
next,
|
next,
|
||||||
{
|
{
|
||||||
scale: 0.55,
|
scale: touch ? 0.8 : 0.55,
|
||||||
clipPath: "inset(0% round 56px)",
|
clipPath: "inset(0% round 56px)",
|
||||||
transformOrigin: () => `50% ${vh() / 2}px`,
|
transformOrigin: () => (touch ? "50% 0%" : `50% ${vh() / 2}px`),
|
||||||
},
|
},
|
||||||
{ scale: 1, clipPath: "inset(0% round 0px)", ease: "power2.out" },
|
{ scale: 1, clipPath: "inset(0% round 0px)", ease: "power2.out" },
|
||||||
0,
|
0,
|
||||||
@@ -180,15 +193,35 @@ export function useCinematicScroll(mainRef: RefObject<HTMLElement | null>) {
|
|||||||
gsap.ticker.add(tick);
|
gsap.ticker.add(tick);
|
||||||
gsap.ticker.lagSmoothing(0);
|
gsap.ticker.lagSmoothing(0);
|
||||||
|
|
||||||
|
// toque: rolagem nativa em outra thread, sem trava de posição pelo JavaScript
|
||||||
|
const touch = window.matchMedia(
|
||||||
|
"(hover: none) and (pointer: coarse)",
|
||||||
|
).matches;
|
||||||
const scenes = Array.from(main.children) as HTMLElement[];
|
const scenes = Array.from(main.children) as HTMLElement[];
|
||||||
|
// Topo de cada cena somando as alturas das anteriores (as cenas são contíguas): com sticky, o
|
||||||
|
// offsetTop de uma cena presa passa a incluir o deslocamento dela.
|
||||||
|
const sceneTops = () => {
|
||||||
|
let top = pageTop(main);
|
||||||
|
return scenes.map((scene) => {
|
||||||
|
const current = top;
|
||||||
|
top += scene.offsetHeight;
|
||||||
|
return current;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const stick = () => {
|
||||||
|
for (const scene of scenes)
|
||||||
|
scene.style.top = `${Math.min(0, vh() - scene.offsetHeight)}px`;
|
||||||
|
};
|
||||||
|
|
||||||
const context = gsap.context(() => {
|
const context = gsap.context(() => {
|
||||||
scenes.forEach((scene, index) => {
|
scenes.forEach((scene, index) => {
|
||||||
// quem entra fica por cima; o hero, que sai por dissolução, fica por cima da cena seguinte
|
// quem entra fica por cima; o hero, que sai por dissolução, fica por cima da cena seguinte
|
||||||
gsap.set(scene, {
|
gsap.set(scene, {
|
||||||
position: "relative",
|
position: "sticky",
|
||||||
zIndex: index === 0 ? 30 : index + 1,
|
zIndex: index === 0 ? 30 : index + 1,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
stick();
|
||||||
TRANSITIONS.forEach((transition, index) => {
|
TRANSITIONS.forEach((transition, index) => {
|
||||||
const prev = scenes[index];
|
const prev = scenes[index];
|
||||||
const next = scenes[index + 1];
|
const next = scenes[index + 1];
|
||||||
@@ -197,8 +230,8 @@ export function useCinematicScroll(mainRef: RefObject<HTMLElement | null>) {
|
|||||||
defaults: { ease: "none", duration: 1 },
|
defaults: { ease: "none", duration: 1 },
|
||||||
scrollTrigger: {
|
scrollTrigger: {
|
||||||
trigger: next,
|
trigger: next,
|
||||||
start: () => pageTop(next) - vh(),
|
start: () => sceneTops()[index + 1] - vh(),
|
||||||
end: () => pageTop(next),
|
end: () => sceneTops()[index + 1],
|
||||||
scrub: true,
|
scrub: true,
|
||||||
invalidateOnRefresh: true,
|
invalidateOnRefresh: true,
|
||||||
// passagem concluída: o recorte já cobre a tela inteira; sem ele a seção pinta normalmente
|
// passagem concluída: o recorte já cobre a tela inteira; sem ele a seção pinta normalmente
|
||||||
@@ -207,23 +240,26 @@ export function useCinematicScroll(mainRef: RefObject<HTMLElement | null>) {
|
|||||||
onEnter: () => next.classList.add("is-drawing"),
|
onEnter: () => next.classList.add("is-drawing"),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
transition(tl, prev, next);
|
transition(tl, prev, next, touch);
|
||||||
});
|
});
|
||||||
}, main);
|
}, main);
|
||||||
|
|
||||||
// Visibilidade pela posição da rolagem, não pelo estado inicial dos tweens: cada cena só existe na
|
// Visibilidade pela posição da rolagem: cada cena só existe na tela da passagem em que entra até a
|
||||||
// tela da transição em que entra até a transição em que sai. Antes ela não vaza por cima da anterior;
|
// passagem em que sai. Folga para o quadro de atraso entre a rolagem nativa e este cálculo: um quarto
|
||||||
// depois não aparece atrás de transparências, e o navegador não pinta o que está coberto.
|
// de tela depois da saída e, no toque, meia tela antes da entrada (lá a cena sobe com o dedo; no
|
||||||
// As posições são lidas a cada quadro (offsetTop é barato com o layout já calculado): guardá-las
|
// desktop ela já nasce presa no topo e só pode aparecer quando a passagem começa).
|
||||||
// deixava a visibilidade errada quando fontes, imagens ou o bloco "Experiência" mudavam a altura.
|
|
||||||
let frame = 0;
|
let frame = 0;
|
||||||
const syncVisibility = () => {
|
const syncVisibility = () => {
|
||||||
frame = 0;
|
frame = 0;
|
||||||
const y = window.scrollY;
|
const y = window.scrollY;
|
||||||
const tops = scenes.map(pageTop);
|
const tops = sceneTops();
|
||||||
scenes.forEach((scene, index) => {
|
scenes.forEach((scene, index) => {
|
||||||
const from = index === 0 ? -Infinity : tops[index] - vh();
|
const from =
|
||||||
const to = index === scenes.length - 1 ? Infinity : tops[index + 1];
|
index === 0 ? -Infinity : tops[index] - vh() * (touch ? 1.5 : 1);
|
||||||
|
const to =
|
||||||
|
index === scenes.length - 1
|
||||||
|
? Infinity
|
||||||
|
: tops[index + 1] + vh() * 0.25;
|
||||||
const visibility = y >= from && y <= to ? "" : "hidden";
|
const visibility = y >= from && y <= to ? "" : "hidden";
|
||||||
if (scene.style.visibility !== visibility)
|
if (scene.style.visibility !== visibility)
|
||||||
scene.style.visibility = visibility;
|
scene.style.visibility = visibility;
|
||||||
@@ -232,8 +268,12 @@ export function useCinematicScroll(mainRef: RefObject<HTMLElement | null>) {
|
|||||||
const queueSync = () => {
|
const queueSync = () => {
|
||||||
if (!frame) frame = requestAnimationFrame(syncVisibility);
|
if (!frame) frame = requestAnimationFrame(syncVisibility);
|
||||||
};
|
};
|
||||||
|
const onRefresh = () => {
|
||||||
|
stick();
|
||||||
|
queueSync();
|
||||||
|
};
|
||||||
syncVisibility();
|
syncVisibility();
|
||||||
ScrollTrigger.addEventListener("refresh", queueSync);
|
ScrollTrigger.addEventListener("refresh", onRefresh);
|
||||||
window.addEventListener("scroll", queueSync, { passive: true });
|
window.addEventListener("scroll", queueSync, { passive: true });
|
||||||
// a página mudou de altura (fontes, imagens, "Experiência" aberta ou fechada): o GSAP remede as passagens
|
// a página mudou de altura (fontes, imagens, "Experiência" aberta ou fechada): o GSAP remede as passagens
|
||||||
let refreshTimer: ReturnType<typeof setTimeout> | undefined;
|
let refreshTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
@@ -279,18 +319,26 @@ export function useCinematicScroll(mainRef: RefObject<HTMLElement | null>) {
|
|||||||
if (!target) return;
|
if (!target) return;
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
// nunca para no meio de uma passagem: no mínimo onde termina a entrada da cena que contém o alvo;
|
// nunca para no meio de uma passagem: no mínimo onde termina a entrada da cena que contém o alvo;
|
||||||
// alvos internos descontam o cabeçalho fixo
|
// alvos internos descontam o cabeçalho fixo (posição relativa à cena, que não muda se ela estiver presa)
|
||||||
const scene = scenes.find((candidate) => candidate.contains(target));
|
const index = scenes.findIndex((candidate) =>
|
||||||
|
candidate.contains(target),
|
||||||
|
);
|
||||||
const offset =
|
const offset =
|
||||||
Number.parseFloat(
|
Number.parseFloat(
|
||||||
getComputedStyle(document.documentElement).scrollPaddingTop,
|
getComputedStyle(document.documentElement).scrollPaddingTop,
|
||||||
) || 0;
|
) || 0;
|
||||||
|
const sceneTop = index < 0 ? 0 : sceneTops()[index];
|
||||||
const top =
|
const top =
|
||||||
!scene || scene === scenes[0]
|
index <= 0
|
||||||
? 0
|
? 0
|
||||||
: Math.max(
|
: Math.max(
|
||||||
pageTop(scene),
|
sceneTop,
|
||||||
scene === target ? 0 : pageTop(target) - offset,
|
scenes[index] === target
|
||||||
|
? 0
|
||||||
|
: sceneTop +
|
||||||
|
pageTop(target) -
|
||||||
|
pageTop(scenes[index]) -
|
||||||
|
offset,
|
||||||
);
|
);
|
||||||
if (location.hash !== url.hash) history.pushState(null, "", url.hash);
|
if (location.hash !== url.hash) history.pushState(null, "", url.hash);
|
||||||
lenis.scrollTo(top, {
|
lenis.scrollTo(top, {
|
||||||
@@ -307,13 +355,16 @@ export function useCinematicScroll(mainRef: RefObject<HTMLElement | null>) {
|
|||||||
|
|
||||||
cleanup = () => {
|
cleanup = () => {
|
||||||
document.removeEventListener("click", onClick, true);
|
document.removeEventListener("click", onClick, true);
|
||||||
ScrollTrigger.removeEventListener("refresh", queueSync);
|
ScrollTrigger.removeEventListener("refresh", onRefresh);
|
||||||
window.removeEventListener("scroll", queueSync);
|
window.removeEventListener("scroll", queueSync);
|
||||||
cancelAnimationFrame(frame);
|
cancelAnimationFrame(frame);
|
||||||
clearTimeout(refreshTimer);
|
clearTimeout(refreshTimer);
|
||||||
resizeObserver.disconnect();
|
resizeObserver.disconnect();
|
||||||
context.revert();
|
context.revert();
|
||||||
for (const scene of scenes) scene.style.visibility = "";
|
for (const scene of scenes) {
|
||||||
|
scene.style.visibility = "";
|
||||||
|
scene.style.top = "";
|
||||||
|
}
|
||||||
gsap.ticker.remove(tick);
|
gsap.ticker.remove(tick);
|
||||||
lenis.destroy();
|
lenis.destroy();
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in new issue
Block a user