perf: Lenis smooth scroll ve scroll skew efektini kaldır
Site geneli ScrollFlow sarmalayıcısı (Lenis lerp'li kaydırma + hıza bağlı skewY) tüm sayfa katmanını her karede transform'luyor, kaydırma performansını düşürüyordu. Yerel tarayıcı kaydırmasına dönüldü; bölüm çapaları mevcut scroll-mt değerleriyle çalışmaya devam eder. lenis bağımlılığı ve data-lenis-prevent işaretleri temizlendi. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -82,8 +82,9 @@ rozet, buton varyantı tasarlanmaz.
|
||||
- **Tamamlanınca:** `tercihProfiliYaz` → çekmece kapanır → `sonucHref` ile
|
||||
`/sonuc` (girişliye `sihirbaz: "1"`, girişsize `hazir: "1"`). Giriş duvarı
|
||||
sihirbazın hemen arkasında değildir; değer önce gösterilir.
|
||||
- **Lenis:** çekmece içeriği ve kaydırma kabı `data-lenis-prevent` taşır;
|
||||
sayfadaki smooth-scroll çekmece içi kaydırmayı ezmez.
|
||||
- **Kaydırma:** sayfa yerel tarayıcı kaydırmasını kullanır (Lenis/skew
|
||||
performans nedeniyle 21.09.2026'da kaldırıldı); çekmece içi kaydırma için
|
||||
ek bir önlem gerekmez.
|
||||
|
||||
## 6. Erişilebilirlik
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
"clsx": "^2.1.1",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"iyzipay": "^2.0.69",
|
||||
"lenis": "^1.3.25",
|
||||
"lucide-react": "^1.25.0",
|
||||
"marked": "^18.0.7",
|
||||
"mermaid": "^11.16.0",
|
||||
|
||||
21
pnpm-lock.yaml
generated
21
pnpm-lock.yaml
generated
@@ -35,9 +35,6 @@ importers:
|
||||
iyzipay:
|
||||
specifier: ^2.0.69
|
||||
version: 2.0.69
|
||||
lenis:
|
||||
specifier: ^1.3.25
|
||||
version: 1.3.25(react@19.2.4)
|
||||
lucide-react:
|
||||
specifier: ^1.25.0
|
||||
version: 1.25.0(react@19.2.4)
|
||||
@@ -4195,20 +4192,6 @@ packages:
|
||||
layout-base@2.0.1:
|
||||
resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==}
|
||||
|
||||
lenis@1.3.25:
|
||||
resolution: {integrity: sha512-mOKxayErlaONK8fm4LN3XNd99Qu4plTpn9h9qf8wxzjGrJDzuD84FYzZ81HCd6ZsWp++VWVwOzL286Pf2s2u4A==}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': '>=3.0.0'
|
||||
react: '>=17.0.0'
|
||||
vue: '>=3.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@nuxt/kit':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -9688,10 +9671,6 @@ snapshots:
|
||||
|
||||
layout-base@2.0.1: {}
|
||||
|
||||
lenis@1.3.25(react@19.2.4):
|
||||
optionalDependencies:
|
||||
react: 19.2.4
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function GirisPage({
|
||||
searchParams: Promise<{ callback?: string; hata?: string }>;
|
||||
}) {
|
||||
return (
|
||||
// -mb-28: layout'taki ScrollFlow sarmalayıcısının pb-28'ini nötrler —
|
||||
// -mb-28: layout'taki içerik sarmalayıcısının pb-28'ini nötrler —
|
||||
// kart tam dvh içinde ortalanır, sayfa scroll etmez (sığmazsa scroll açılır).
|
||||
<div className="-mb-28 flex min-h-dvh flex-col bg-slate-50 text-slate-900">
|
||||
<main className="mx-auto flex w-full max-w-md flex-1 flex-col items-stretch justify-center px-6 py-8">
|
||||
|
||||
@@ -252,7 +252,5 @@
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
/* scroll-behavior: smooth KULLANMA — Lenis (ScrollFlow, anchors: true)
|
||||
kaydırmayı zaten yumuşatıyor; ikisi birlikte çift yumuşatma yapar. */
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import { TercihProfiliKapisiLazy } from "@/features/liste/components/tercih-prof
|
||||
import { HeaderGate } from "@/components/header-gate";
|
||||
import { ProgressiveBlur } from "@/components/ui/skiper-ui/skiper41";
|
||||
import { DevPanelMount } from "@/components/dev/dev-panel-mount";
|
||||
import { ScrollFlow } from "@/components/scroll-flow";
|
||||
import { JsonLd, organizationJsonLd, webSiteJsonLd, SITE_URL } from "@/lib/seo";
|
||||
import "./globals.css";
|
||||
|
||||
@@ -79,10 +78,8 @@ export default function RootLayout({
|
||||
return (
|
||||
<html
|
||||
lang="tr"
|
||||
// `h-full` VERME — Lenis içerik yüksekliğini <html>'e bağlı ResizeObserver
|
||||
// ile izliyor; html sabit %100 kalınca lazy yüklenen içerik sonrası limit
|
||||
// bayatlıyor ve sayfa sonunda scroll kilitleniyor. html auto yükseklikte
|
||||
// içerikle büyümeli, viewport tabanı body'deki min-h-dvh'den gelir.
|
||||
// `h-full` verme: html auto yükseklikte içerikle büyür, viewport tabanı
|
||||
// body'deki min-h-dvh'den gelir.
|
||||
className={`${calSans.variable} ${clarityCity.variable} ${geistMono.variable} ${bricolage.variable} antialiased`}
|
||||
>
|
||||
<body className="min-h-dvh flex flex-col bg-slate-50">
|
||||
@@ -107,9 +104,9 @@ export default function RootLayout({
|
||||
<SiteTopBanner />
|
||||
<SiteHeader />
|
||||
</HeaderGate>
|
||||
<ScrollFlow className="flex min-h-0 flex-1 flex-col pb-28 print:pb-0">
|
||||
<div className="flex min-h-0 flex-1 flex-col pb-28 print:pb-0">
|
||||
{children}
|
||||
</ScrollFlow>
|
||||
</div>
|
||||
{/* Manuel 24'lük liste çekmecesi + "+" uçuş katmanı (navbar'dan açılır) */}
|
||||
<ListeCekmecesiLazy />
|
||||
{/* Girişsiz + 3 seçimli kullanıcıya "listen yalnızca bu tarayıcıda" banner'ı */}
|
||||
|
||||
@@ -17,7 +17,7 @@ export default function OdemePage({
|
||||
searchParams: Promise<{ siparis?: string }>;
|
||||
}) {
|
||||
return (
|
||||
// -mb-28: layout'taki ScrollFlow sarmalayıcısının pb-28'ini nötrler — o
|
||||
// -mb-28: layout'taki içerik sarmalayıcısının pb-28'ini nötrler — o
|
||||
// padding gizlenen alt blur katmanı içindi, checkout'ta krom kapalı.
|
||||
<div className="-mb-28 flex min-h-dvh flex-col bg-slate-50 text-slate-900">
|
||||
<OdemeUstSerit />
|
||||
|
||||
@@ -178,7 +178,6 @@ export function DevPanel() {
|
||||
</div>
|
||||
|
||||
<div
|
||||
data-lenis-prevent
|
||||
className="flex flex-col gap-4 overflow-y-auto p-4 text-sm"
|
||||
>
|
||||
{/* Oturum */}
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { type ReactNode } from "react";
|
||||
import { ReactLenis } from "lenis/react";
|
||||
import {
|
||||
motion,
|
||||
useReducedMotion,
|
||||
useScroll,
|
||||
useSpring,
|
||||
useTransform,
|
||||
useVelocity,
|
||||
} from "motion/react";
|
||||
|
||||
/**
|
||||
* Vitrin sayfalarının akışkan scroll efekti: Lenis ile lerp'li (yavaşlatılmış,
|
||||
* momentumlu) kaydırma + scroll hızına bağlı içerik bükülmesi (skew).
|
||||
* Hızlı kaydırdıkça sayfa hafifçe yana yatar, durunca yayla yerine oturur.
|
||||
*
|
||||
* Sohbet ve liste gibi iç scroll alanı olan uygulama sayfalarında KULLANMA —
|
||||
* Lenis tekerlek olayını sayfaya yönlendirdiği için iç scroll'u bozar,
|
||||
* transform da `fixed` konumlu çocukları koparır.
|
||||
*
|
||||
* Hareket azaltılmışsa hem yumuşatma hem bükülme tamamen kapalıdır.
|
||||
*/
|
||||
export function ScrollFlow({
|
||||
children,
|
||||
className,
|
||||
maxSkew = 3,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
/** Derece cinsinden en fazla bükülme açısı. */
|
||||
maxSkew?: number;
|
||||
}) {
|
||||
const reduceMotion = useReducedMotion();
|
||||
const { scrollY } = useScroll();
|
||||
const velocity = useVelocity(scrollY);
|
||||
|
||||
// Önce hızı kesintisiz bir hedef açıya dönüştür, sonra doğrudan görünen
|
||||
// açıyı yumuşat. Böylece eşik/clamp kaynaklı ani hız değişimleri yaya girmez.
|
||||
const targetSkew = useTransform(velocity, (v) => {
|
||||
const RESPONSE = 1800;
|
||||
return Math.tanh(v / RESPONSE) * maxSkew;
|
||||
});
|
||||
const smoothSkew = useSpring(targetSkew, {
|
||||
stiffness: 240,
|
||||
damping: 26,
|
||||
mass: 0.7,
|
||||
restDelta: 0.005,
|
||||
restSpeed: 0.01,
|
||||
});
|
||||
|
||||
// Yuvarlama görsel basamaklanma üretir. Tam hassasiyetle çiz; yalnızca açı
|
||||
// algılanamayacak kadar küçüldüğünde transform'u kaldırarak dev sayfa
|
||||
// katmanının GPU belleğinde boşuna tutulmasını önle.
|
||||
const transform = useTransform(smoothSkew, (v) => {
|
||||
if (Math.abs(v) < 0.01) return "none";
|
||||
return `skewY(${v}deg)`;
|
||||
});
|
||||
|
||||
// Hareket azaltılmışsa da AYNI ağaç render edilir (server/client farklı
|
||||
// ağaç hydration uyumsuzluğu yaratıyordu); efektler koşullu kapatılır:
|
||||
// Lenis tekerlek yumuşatması kapalı, skew transform'u hiç bağlanmaz.
|
||||
return (
|
||||
<ReactLenis
|
||||
root
|
||||
options={{ lerp: 0.09, anchors: true, smoothWheel: !reduceMotion }}
|
||||
>
|
||||
<motion.div
|
||||
className={className}
|
||||
style={reduceMotion ? undefined : { transform }}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</ReactLenis>
|
||||
);
|
||||
}
|
||||
@@ -26,7 +26,6 @@ function ChatContainerRoot({
|
||||
return (
|
||||
<StickToBottom
|
||||
className={cn("flex overflow-y-auto", className)}
|
||||
data-lenis-prevent
|
||||
resize="smooth"
|
||||
initial="instant"
|
||||
role="log"
|
||||
|
||||
@@ -60,7 +60,6 @@ function DialogContent({
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
data-slot="dialog-content"
|
||||
data-lenis-prevent
|
||||
className={cn(
|
||||
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
||||
className
|
||||
|
||||
@@ -68,7 +68,6 @@ function SelectContent({
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
data-slot="select-content"
|
||||
data-lenis-prevent
|
||||
data-align-trigger={position === "item-aligned"}
|
||||
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
|
||||
position={position}
|
||||
|
||||
@@ -8,9 +8,9 @@ const getClientSnapshot = () => true;
|
||||
const getServerSnapshot = () => false;
|
||||
|
||||
/**
|
||||
* Viewport'a sabit kalması gereken öğeleri, transform uygulanan sayfa
|
||||
* katmanının dışına taşır. Böylece üst katmandaki scroll skew animasyonu
|
||||
* `position: fixed` koordinat sistemini değiştirmez.
|
||||
* Viewport'a sabit kalması gereken öğeleri sayfa katmanının dışına, doğrudan
|
||||
* body'ye taşır. Böylece üst katmanlardaki bir transform (ör. Parallax/Reveal)
|
||||
* `position: fixed` koordinat sistemini değiştiremez.
|
||||
*/
|
||||
export function ViewportPortal({ children }: { children: ReactNode }) {
|
||||
const mounted = useSyncExternalStore(
|
||||
|
||||
@@ -115,7 +115,6 @@ export function ListeCekmecesi() {
|
||||
onAnimationEnd={(open) => setOturdu(open)}
|
||||
>
|
||||
<DrawerContent
|
||||
data-lenis-prevent
|
||||
className="min-h-0 max-h-[92dvh] bg-white sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl"
|
||||
>
|
||||
<DrawerHeader className="border-b border-slate-100 text-left md:text-left">
|
||||
@@ -182,7 +181,6 @@ export function ListeCekmecesi() {
|
||||
</DrawerHeader>
|
||||
|
||||
<div
|
||||
data-lenis-prevent
|
||||
className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto px-4 py-4"
|
||||
>
|
||||
{liste.length === 0 ? (
|
||||
|
||||
@@ -190,7 +190,6 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) {
|
||||
|
||||
<Drawer open={acik} onOpenChange={acikDegisti} handleOnly>
|
||||
<DrawerContent
|
||||
data-lenis-prevent
|
||||
className="max-h-[92dvh] bg-white sm:mx-auto sm:max-w-2xl sm:border-x sm:border-slate-200 sm:shadow-2xl"
|
||||
>
|
||||
{/* Görünür başlıklar adıma göre değişir; erişilebilir ad sabit kalır. */}
|
||||
@@ -207,7 +206,6 @@ export function CtaSiraForm({ baslik }: { baslik?: string }) {
|
||||
</DrawerClose>
|
||||
|
||||
<div
|
||||
data-lenis-prevent
|
||||
className="min-h-0 flex-1 overflow-y-auto px-4 pt-5 pb-[max(1.25rem,env(safe-area-inset-bottom))] sm:px-6"
|
||||
>
|
||||
{adim === "sira" ? (
|
||||
|
||||
Reference in New Issue
Block a user