Kunci jawaban 19 Latihan Editor Pixel. Ide solusinya; sesuaikan dengan nama di kode editor-mu.
19.1 Pintasan Keyboard
// di komponen puncak (yang punya .dom dan dispatch)
this.dom.tabIndex = 0; // supaya bisa fokus & menerima keydown
this.dom.addEventListener("keydown", event => {
if (event.ctrlKey && event.key == "z") {
event.preventDefault();
dispatch({undo: true});
} else {
// cocokkan huruf pertama nama alat
for (let nama of Object.keys(alatTersedia)) {
if (event.key == nama[0]) {
event.preventDefault();
dispatch({tool: nama});
break;
}
}
}
});Kenapa: elemen biasa gak menerima "keydown" kecuali bisa fokus, makanya diberi tabIndex = 0. Ctrl-Z memicu dispatch({undo: true}). Untuk memilih alat, kita cocokkan event.key dengan huruf pertama tiap nama alat lalu dispatch({tool}). Semua perubahan tetap lewat dispatch — sesuai pola state-terpusat, jadi UI ikut tersinkron sendiri.
→ Soal: 19 Latihan Editor Pixel > 19.1 Pintasan Keyboard
19.2 Menggambar Efisien
// di dalam PictureCanvas
syncState(gambar) {
if (this.gambar == gambar) return;
let lama = this.gambar;
this.gambar = gambar;
for (let y = 0; y < gambar.height; y++) {
for (let x = 0; x < gambar.width; x++) {
let warna = gambar.pixel(x, y);
if (!lama || lama.pixel(x, y) != warna) {
this.cx.fillStyle = warna;
this.cx.fillRect(x * skala, y * skala, skala, skala);
}
}
}
}Kenapa: kita simpan gambar terakhir (this.gambar). Kalau gambar barunya objek yang sama (==), gak ada yang berubah — langsung keluar (ini murah karena Picture immutable, jadi cukup bandingkan identitas). Kalau beda, kita bandingkan per piksel dengan gambar lama dan hanya menggambar ulang piksel yang warnanya berubah, bukan seluruh kanvas.
→ Soal: 19 Latihan Editor Pixel > 19.2 Menggambar Efisien
19.3 Lingkaran
function circle(mulai, state, dispatch) {
function gambar(pos) {
let jari = Math.sqrt(
(pos.x - mulai.x) ** 2 + (pos.y - mulai.y) ** 2);
let bulat = Math.ceil(jari);
let piksel = [];
for (let dy = -bulat; dy <= bulat; dy++) {
for (let dx = -bulat; dx <= bulat; dx++) {
if (dx * dx + dy * dy > jari * jari) continue;
let x = mulai.x + dx, y = mulai.y + dy;
if (x < 0 || x >= state.picture.width ||
y < 0 || y >= state.picture.height) continue;
piksel.push({x, y, color: state.color});
}
}
dispatch({picture: state.picture.draw(piksel)});
}
gambar(mulai);
return gambar;
}Kenapa: alat = fungsi yang mengembalikan handler gambar untuk gerakan lanjutan. mulai adalah pusat lingkaran, dan jari-jari = jarak pusat ke posisi kursor sekarang (Pythagoras). Kita periksa tiap piksel dalam kotak pembatas dan hanya ambil yang dx² + dy² ≤ jari² (di dalam lingkaran), lalu dispatch gambar baru. Tiap gerakan dihitung dari state.picture yang tetap (state sebelum menyeret), jadi lingkarannya “digambar ulang” mengikuti kursor, bukan menumpuk.