İçeriğe geç
academia.sh

Ders 13 / 23

Dönüşümler

Öteleme, ölçekleme, döndürme ve eğme fonksiyonlarının matris karşılığı; fonksiyon sırasının sonucu değiştirmesi, dönüşüm kaynağının etkisi ve dönüşümün düzenden sonra uygulanması.

İçindekiler

Bir önceki ders kullanıcı tercihlerini okudu: renk şeması ve hareket azaltma. Renk şeması tercihinin neyi değiştirdiği bellidir — renk bildirimlerini. Hareket azaltma tercihi ise “yer değiştirme üreten” bildirimleri kaldırır ve o bildirimlerin ne olduğu henüz tanımlanmadı.

Bu konu o tanımı kurar. Sayfadaki hareket üç katmandan gelir: kutuyu kendi yerinde öteleyen, ölçekleyen ve döndüren geometrik işlemler, iki durum arasındaki değişimin zamana yayılması ve zaman çizelgesiyle sürülen anahtar kare dizileri. Bu ders birincisini ele alır. Üç işlemin ortak bir adı ve ortak bir matematiği vardır; ikisi de görüldüğünde hangi hareketin neyi hareket ettirdiği ölçülebilir bir soru hâline gelir.

Dönüşüm Düzenden Sonra Uygulanır

Dönüşüm (transform) bir ögenin kutusunu ötelemek, ölçeklemek, döndürmek veya eğmek için kullanılır. Belirleyici özelliği, düzen hesabına dokunmamasıdır.

Sıra şudur: tarayıcı düzeni hesaplar, kutunun yerini ve ölçüsünü bulur, sonra dönüşümü o kutuya uygular. Ögenin düzendeki yeri değişmez; komşuları hiçbir şey olmamış gibi durur. Bir ögeyi transform: translateX(40px) ile sağa itmek, margin-inline-start: 40px ile itmekten bu yüzden farklıdır: ikincisi kapsayan bloğun içindeki bütün akışı yeniden hesaplatır, birincisi yalnız o kutunun çizimini değiştirir.

Bunun üç sonucu vardır:

  1. Dönüşmüş öge komşularının üzerine taşabilir; taşma hesabı düzenden gelir, dönüşümden değil.
  2. Yüzde değerleri ögenin kendi kenarlık kutusuna göre okunur. translateX(50%) ögenin kendi genişliğinin yarısıdır; oysa left: 50% kapsayan bloğun genişliğinin yarısıdır.
  3. none dışında bir değer taşıyan dönüşüm, yeni bir yığılma bağlamı kurar ve içindeki fixed konumlu torunlar için kapsayan blok olur. Bu kural CSS ile Görsel Sunum kursunun Yığılma Bağlamı dersinde tanımlanmıştı; hareket yazarken sık sık geri döner.

Üçüncü madde bir hata kaynağıdır. Sayfanın tamamını saran bir kapsayıcıya küçük bir dönüşüm yazmak, o kapsayıcının içindeki sabit konumlu bir başlığı ekrana değil kapsayıcıya sabitlenmiş duruma getirir.

Fonksiyonlar ve Matris Karşılıkları

Dönüşüm özelliği bir fonksiyon listesi alır. Listedeki her fonksiyon bir 3×33 \times 3 matrise karşılık gelir ve liste bu matrislerin çarpımıdır. İki boyutta matrisin son satırı her zaman (0,0,1)(0, 0, 1) olduğu için altı sayı yeterlidir; matrix() fonksiyonunun aldığı sayılar bunlardır.

(acebdf001)(xy1)=(ax+cy+ebx+dy+f)\begin{pmatrix} a & c & e \\ b & d & f \\ 0 & 0 & 1 \end{pmatrix} \begin{pmatrix} x \\ y \\ 1 \end{pmatrix} = \begin{pmatrix} ax + cy + e \\ bx + dy + f \end{pmatrix}

Bir nokta bu çarpımla yeni yerine taşınır. Aşağıdaki program fonksiyonları matrise çevirir, listeleri çarpar ve bir kartın dört köşesini izler.

// donusum.mjs — donusum fonksiyonlarinin matris karsiligi ve sira bagimliligi
// matris [a, b, c, d, e, f] -> | a c e |
//                              | b d f |
//                              | 0 0 1 |
const carp = (m, n) => [
  m[0] * n[0] + m[2] * n[1],
  m[1] * n[0] + m[3] * n[1],
  m[0] * n[2] + m[2] * n[3],
  m[1] * n[2] + m[3] * n[3],
  m[0] * n[4] + m[2] * n[5] + m[4],
  m[1] * n[4] + m[3] * n[5] + m[5],
];

const rad = (d) => (d * Math.PI) / 180;
const oteleme = (tx, ty) => [1, 0, 0, 1, tx, ty];
const olcekleme = (sx, sy) => [sx, 0, 0, sy, 0, 0];
const dondurme = (d) => [Math.cos(rad(d)), Math.sin(rad(d)), -Math.sin(rad(d)), Math.cos(rad(d)), 0, 0];
const egmeX = (d) => [1, 0, Math.tan(rad(d)), 1, 0, 0];
const egmeY = (d) => [1, Math.tan(rad(d)), 0, 1, 0, 0];

const BIRIM = [1, 0, 0, 1, 0, 0];
const zincir = (liste) => liste.reduce((t, m) => carp(t, m), BIRIM);
const yaz = (m) => "matrix(" + m.map((v) => (Math.abs(v) < 1e-9 ? 0 : +v.toFixed(4))).join(", ") + ")";
const nokta = (m, [x, y]) => [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
const p = ([x, y]) => `(${x.toFixed(1)}, ${y.toFixed(1)})`;

console.log("--- tek fonksiyonlarin matris karsiligi ---");
console.log("translate(40px, 12px) ->", yaz(oteleme(40, 12)));
console.log("scale(1.25, 1.25)     ->", yaz(olcekleme(1.25, 1.25)));
console.log("rotate(30deg)         ->", yaz(dondurme(30)));
console.log("skewX(12deg)          ->", yaz(egmeX(12)));
console.log("skewY(12deg)          ->", yaz(egmeY(12)));

console.log("\n--- sira bagimliligi: ayni iki fonksiyon, iki yazim ---");
const A = zincir([oteleme(120, 0), dondurme(30)]);
const B = zincir([dondurme(30), oteleme(120, 0)]);
console.log("translate(120px, 0) rotate(30deg) ->", yaz(A));
console.log("rotate(30deg) translate(120px, 0) ->", yaz(B));

const KOSE = [[0, 0], [220, 0], [220, 120], [0, 120]];   // 220x120 birimlik kart
console.log("\nkose noktalari (sol ust, sag ust, sag alt, sol alt):");
console.log("  yazim 1:", KOSE.map((k) => p(nokta(A, k))).join(" "));
console.log("  yazim 2:", KOSE.map((k) => p(nokta(B, k))).join(" "));

console.log("\n--- transform-origin: T(o) * M * T(-o) ---");
const ORIJIN = [[0, 0], [110, 60], [220, 120]];          // sol ust, merkez, sag alt
for (const [ox, oy] of ORIJIN) {
  const M = zincir([oteleme(ox, oy), dondurme(30), oteleme(-ox, -oy)]);
  console.log(`origin (${ox}, ${oy})`.padEnd(18), yaz(M));
  console.log("  koseler:", KOSE.map((k) => p(nokta(M, k))).join(" "));
}

console.log("\n--- olcekleme ve otelemenin sirasi ---");
const C = zincir([olcekleme(2, 2), oteleme(50, 0)]);
const D = zincir([oteleme(50, 0), olcekleme(2, 2)]);
console.log("scale(2) translate(50px, 0) ->", yaz(C), " sol ust kose:", p(nokta(C, [0, 0])));
console.log("translate(50px, 0) scale(2) ->", yaz(D), " sol ust kose:", p(nokta(D, [0, 0])));

console.log("\n--- tek matrise indirgeme: uc fonksiyon, tek carpim ---");
const E = zincir([oteleme(0, -8), dondurme(-3), olcekleme(1.04, 1.04)]);
console.log("translate(0, -8px) rotate(-3deg) scale(1.04) ->", yaz(E));
console.log("kart koseleri:", KOSE.map((k) => p(nokta(E, k))).join(" "));
--- tek fonksiyonlarin matris karsiligi ---
translate(40px, 12px) -> matrix(1, 0, 0, 1, 40, 12)
scale(1.25, 1.25)     -> matrix(1.25, 0, 0, 1.25, 0, 0)
rotate(30deg)         -> matrix(0.866, 0.5, -0.5, 0.866, 0, 0)
skewX(12deg)          -> matrix(1, 0, 0.2126, 1, 0, 0)
skewY(12deg)          -> matrix(1, 0.2126, 0, 1, 0, 0)

--- sira bagimliligi: ayni iki fonksiyon, iki yazim ---
translate(120px, 0) rotate(30deg) -> matrix(0.866, 0.5, -0.5, 0.866, 120, 0)
rotate(30deg) translate(120px, 0) -> matrix(0.866, 0.5, -0.5, 0.866, 103.923, 60)

kose noktalari (sol ust, sag ust, sag alt, sol alt):
  yazim 1: (120.0, 0.0) (310.5, 110.0) (250.5, 213.9) (60.0, 103.9)
  yazim 2: (103.9, 60.0) (294.4, 170.0) (234.4, 273.9) (43.9, 163.9)

--- transform-origin: T(o) * M * T(-o) ---
origin (0, 0)      matrix(0.866, 0.5, -0.5, 0.866, 0, 0)
  koseler: (0.0, 0.0) (190.5, 110.0) (130.5, 213.9) (-60.0, 103.9)
origin (110, 60)   matrix(0.866, 0.5, -0.5, 0.866, 44.7372, -46.9615)
  koseler: (44.7, -47.0) (235.3, 63.0) (175.3, 167.0) (-15.3, 57.0)
origin (220, 120)  matrix(0.866, 0.5, -0.5, 0.866, 89.4744, -93.923)
  koseler: (89.5, -93.9) (280.0, 16.1) (220.0, 120.0) (29.5, 10.0)

--- olcekleme ve otelemenin sirasi ---
scale(2) translate(50px, 0) -> matrix(2, 0, 0, 2, 100, 0)  sol ust kose: (100.0, 0.0)
translate(50px, 0) scale(2) -> matrix(2, 0, 0, 2, 50, 0)  sol ust kose: (50.0, 0.0)

--- tek matrise indirgeme: uc fonksiyon, tek carpim ---
translate(0, -8px) rotate(-3deg) scale(1.04) -> matrix(1.0386, -0.0544, 0.0544, 1.0386, 0, -8)
kart koseleri: (0.0, -8.0) (228.5, -20.0) (235.0, 104.7) (6.5, 116.6)

İlk blok dört ailenin matriste nereye yazıldığını gösteriyor. Öteleme (translate) yalnız son iki sayıya, yani ee ve ff değerlerine dokunur; sol üstteki 2×22 \times 2 bölüm birim matris kalır. Ölçekleme (scale) köşegene yazar. Döndürme (rotate) dört hücreyi de kullanır ve değerler açının kosinüs ile sinüsüdür. Eğme (skew) köşegen dışı tek bir hücreyi doldurur: skewX üstteki cc hücresini, skewY alttaki bb hücresini.

Eğmenin bir yan sonucu vardır: köşegen dışı bir hücre dolduğunda dik açılar korunmaz. Ölçekleme ve döndürme dikdörtgeni dikdörtgen bırakır, eğme paralelkenara çevirir.

Sıra Bağımlılığı

Matris çarpımı değişmeli değildir. Çıktının ikinci bloğu bunu iki yazımla gösteriyor. Aynı iki fonksiyon, farklı sırada yazıldığında iki farklı matris veriyor: birinde öteleme (120,0)(120, 0), diğerinde (103,9; 60)(103{,}9;\ 60).

Kural şudur: liste soldan sağa çarpılır, noktaya ise sağdan sola uygulanır. Son yazılan fonksiyon noktaya ilk dokunandır.

  • translate(120px, 0) rotate(30deg) — kart önce kendi yerinde döner, sonra dönmüş hâliyle sayfanın eksenlerinde 120 birim sağa gider. Öteleme miktarı dönmeden etkilenmez.
  • rotate(30deg) translate(120px, 0) — kart önce 120 birim ötelenir, sonra bu öteleme de dâhil her şey döner. Öteleme dönmüş eksende gerçekleşmiş gibi görünür: 120 birimlik hareket (103,9; 60)(103{,}9;\ 60) noktasına düşer, çünkü 120cos30°103,9120 \cos 30° \approx 103{,}9 ve 120sin30°=60120 \sin 30° = 60.

Ölçekleme ile öteleme çiftinde aynı olay daha çıplaktır. scale(2) translate(50px, 0) yazımında öteleme de ikiye katlanır ve kart 100 birim gider; ters yazımda 50 birim gider. Ölçeklemeden sonra yazılan uzunluklar ölçeklenmiş birimlerdedir.

Bu, hareket yazarken en sık kırılan kuraldır. Bir kartın hem büyüyüp hem yukarı kalkması isteniyorsa iki fonksiyonun sırası, hareketin yönünü ve miktarını belirler.

Dönüşüm Kaynağı

Döndürme ve ölçekleme sabit bir noktayı yerinde bırakır. Bu noktaya dönüşüm kaynağı (transform-origin) denir ve varsayılan değeri kutunun merkezidir.

Matris karşılığı ayrı bir mekanizma değildir: kaynak, dönüşümü üç adımlık bir zincire çevirir. Kaynak noktası başlangıca taşınır, dönüşüm uygulanır, nokta yerine geri götürülür. Çıktının üçüncü bloğu üç kaynak için bu zinciri hesaplıyor.

Sonuçların okunuşu şöyledir: kaynak sol üst köşedeyken (0,0)(0, 0) noktası yerinde kalıyor; kaynak sağ alt köşedeyken (220,120)(220, 120) noktası yerinde kalıyor. Aradaki fark ee ve ff değerlerine yansıyor — döndürme matrisinin ilk dört sayısı üç durumda da aynı.

transform-origin iki ya da üç değer alır; yüzdeler ögenin kendi kenarlık kutusuna göre okunur. Kart açılırken sol kenarından açılsın isteniyorsa kaynak left center yazılır.

Üç Boyuta Geçiş

İki boyutlu listeye translateZ, rotateX, rotateY gibi bir fonksiyon girdiğinde matris 4×44 \times 4 olur ve matrix3d() on altı sayı alır. Kural aynı kalır: liste çarpılır, noktaya sağdan sola uygulanır.

Üç boyutta iki ek bildirim devreye girer. perspective bir bakış uzaklığı tanımlar; küçük değer güçlü, büyük değer zayıf bir derinlik etkisi verir. Bu değer ögenin kendisine transform listesinde ya da ana ögesine perspective özelliğiyle yazılabilir; ikincisi kardeş ögelerin ortak bir bakış noktasını paylaşmasını sağlar.

transform-style: preserve-3d ise alt ögelerin düzleştirilmeden kendi derinliklerini korumasını söyler. Varsayılan flat değeri, alt ağacı ana ögenin düzlemine bastırır. backface-visibility: hidden bildirimi, ögenin arka yüzü izleyiciye döndüğünde çizilmemesini bildirir — iki yüzü olan bir kartın ters yüzünü gizlemek için kullanılır.

Üç boyutlu dönüşümlerin bir bedeli vardır ve bu konunun dördüncü dersinde ele alınır: derinlik taşıyan bir öge, çizimde kendi katmanına ayrılır.

İstasyon Sayfasında Dönüşüm

Ölçüm kartlarına dokunma göstergesi ekleyelim. Kart, işaretçi üzerine geldiğinde bir miktar yükselsin ve çok az büyüsün.

/* hareket.css — 1. adım: kart vurgusu */
.olcum-kartlari > .kart {
  transform-origin: center bottom;
}

.olcum-kartlari > .kart:hover,
.olcum-kartlari > .kart:focus-within {
  transform: translateY(-8px) scale(1.04);
}

Sıra bilinçlidir. translateY(-8px) önce yazıldığı için 8 birimlik yükselme ölçeklenmemiştir; ters sırada yazılsaydı yükselme 8×1,04=8,328 \times 1{,}04 = 8{,}32 birim olurdu. Fark küçüktür, ama aynı kural 200 birimlik bir öteleme için sekiz birimlik bir sapma üretir.

transform-origin: center bottom kartın alt kenarını sabitler: kart kendi tabanından büyür, komşu kartın üzerine simetrik olarak taşmaz.

:focus-within sözde sınıfı, :hover ile aynı bildirimi klavye kullanıcısına da verir. Kart içindeki bir bağlantı odak aldığında kart aynı biçimde yükselir.

Bu dosya duzen.css dosyasının yanına konur. Üçüncü bir dosyanın gerekip gerekmediği — görsel, düzen ve hareket bildirimlerinin nasıl bölüneceği — kursun son konusunun sorusudur.

Özet

  • Dönüşüm düzen hesabından sonra uygulanır; ögenin düzendeki yeri ve komşularının konumu değişmez, yalnızca çizilen kutu taşınır.
  • Dönüşüm listesindeki her fonksiyon bir matristir; liste bu matrislerin çarpımıdır ve matrix() yazımı iki boyutlu bir dönüşümün altı sayısını verir.
  • Çarpım değişmeli olmadığı için fonksiyon sırası sonucu değiştirir: liste soldan sağa çarpılır, noktaya sağdan sola uygulanır ve ölçeklemeden sonra yazılan uzunluklar ölçeklenmiş birimlerdedir.
  • transform-origin ayrı bir mekanizma değil, dönüşümün önüne ve arkasına eklenen bir öteleme çiftidir; varsayılanı kutunun merkezidir.
  • none dışında bir dönüşüm değeri yığılma bağlamı kurar ve sabit konumlu torunlar için kapsayan blok olur.

Sonraki Adım

Bu dersin bildirimleri hareketi anlık yapar: işaretçi kartın üzerine geldiği anda kart sekiz birim yukarıda ve yüzde dört büyüktür, arada bir şey yoktur. Oysa hareket dediğimiz şey iki durum arasındaki yoldur. Sonraki ders bu yolu tanımlayan bildirimleri ele alır: hangi özellik ne kadar sürede, hangi hız eğrisiyle eski değerinden yenisine gider?

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat