14 KiB
Projek Terrarium Bahagian 3: Manipulasi DOM dan Penutupan (Closure)
Sketchnote oleh Tomomi Imura
Kuiz Pra-Kuliah
Pengenalan
Memanipulasi DOM, atau "Document Object Model", adalah aspek penting dalam pembangunan web. Menurut MDN, "Document Object Model (DOM) adalah representasi data bagi objek-objek yang membentuk struktur dan kandungan dokumen di web." Cabaran dalam manipulasi DOM di web sering menjadi sebab utama penggunaan rangka kerja JavaScript berbanding JavaScript biasa untuk menguruskan DOM, tetapi kita akan cuba melakukannya sendiri!
Selain itu, pelajaran ini akan memperkenalkan idea tentang penutupan JavaScript, yang boleh difahami sebagai fungsi yang tertutup oleh fungsi lain supaya fungsi dalaman mempunyai akses kepada skop fungsi luaran.
Penutupan JavaScript adalah topik yang luas dan kompleks. Pelajaran ini hanya menyentuh idea asas bahawa dalam kod terrarium ini, anda akan menemui penutupan: fungsi dalaman dan fungsi luaran yang dibina dengan cara yang membolehkan fungsi dalaman mengakses skop fungsi luaran. Untuk maklumat lebih lanjut tentang cara ini berfungsi, sila lawati dokumentasi yang luas.
Kita akan menggunakan penutupan untuk memanipulasi DOM.
Fikirkan DOM sebagai pokok, yang mewakili semua cara dokumen halaman web boleh dimanipulasi. Pelbagai API (Antara Muka Program Aplikasi) telah ditulis supaya pengaturcara, menggunakan bahasa pengaturcaraan pilihan mereka, boleh mengakses DOM dan mengedit, mengubah, menyusun semula, dan menguruskannya.
Representasi DOM dan markup HTML yang merujuknya. Daripada Olfa Nasraoui
Dalam pelajaran ini, kita akan melengkapkan projek terrarium interaktif kita dengan mencipta JavaScript yang membolehkan pengguna memanipulasi tumbuhan di halaman.
Prasyarat
Anda seharusnya telah membina HTML dan CSS untuk terrarium anda. Pada akhir pelajaran ini, anda akan dapat memindahkan tumbuhan masuk dan keluar dari terrarium dengan menyeretnya.
Tugasan
Dalam folder terrarium anda, buat fail baru bernama script.js
. Import fail tersebut dalam bahagian <head>
:
<script src="./script.js" defer></script>
Nota: gunakan
defer
semasa mengimport fail JavaScript luaran ke dalam fail HTML supaya JavaScript hanya dilaksanakan selepas fail HTML dimuat sepenuhnya. Anda juga boleh menggunakan atributasync
, yang membolehkan skrip dilaksanakan semasa fail HTML sedang diproses, tetapi dalam kes kita, adalah penting untuk elemen HTML tersedia sepenuhnya untuk diseret sebelum kita membenarkan skrip seretan dilaksanakan.
Elemen DOM
Perkara pertama yang perlu anda lakukan ialah mencipta rujukan kepada elemen-elemen yang ingin anda manipulasi dalam DOM. Dalam kes kita, ia adalah 14 tumbuhan yang sedang menunggu di bar sisi.
Tugasan
dragElement(document.getElementById('plant1'));
dragElement(document.getElementById('plant2'));
dragElement(document.getElementById('plant3'));
dragElement(document.getElementById('plant4'));
dragElement(document.getElementById('plant5'));
dragElement(document.getElementById('plant6'));
dragElement(document.getElementById('plant7'));
dragElement(document.getElementById('plant8'));
dragElement(document.getElementById('plant9'));
dragElement(document.getElementById('plant10'));
dragElement(document.getElementById('plant11'));
dragElement(document.getElementById('plant12'));
dragElement(document.getElementById('plant13'));
dragElement(document.getElementById('plant14'));
Apa yang sedang berlaku di sini? Anda sedang merujuk dokumen dan mencari melalui DOM untuk mencari elemen dengan Id tertentu. Ingat dalam pelajaran pertama tentang HTML bahawa anda memberikan Id individu kepada setiap imej tumbuhan (id="plant1"
)? Sekarang anda akan menggunakan usaha tersebut. Selepas mengenal pasti setiap elemen, anda menghantar item tersebut kepada fungsi bernama dragElement
yang akan anda bina sebentar lagi. Dengan itu, elemen dalam HTML kini boleh diseret, atau akan boleh diseret tidak lama lagi.
✅ Mengapa kita merujuk elemen berdasarkan Id? Mengapa tidak berdasarkan kelas CSS mereka? Anda mungkin merujuk pelajaran sebelumnya tentang CSS untuk menjawab soalan ini.
Penutupan (Closure)
Sekarang anda bersedia untuk mencipta penutupan dragElement
, yang merupakan fungsi luaran yang menutup fungsi dalaman atau beberapa fungsi dalaman (dalam kes kita, kita akan mempunyai tiga).
Penutupan berguna apabila satu atau lebih fungsi perlu mengakses skop fungsi luaran. Berikut adalah contoh:
function displayCandy(){
let candy = ['jellybeans'];
function addCandy(candyType) {
candy.push(candyType)
}
addCandy('gumdrops');
}
displayCandy();
console.log(candy)
Dalam contoh ini, fungsi displayCandy
mengelilingi fungsi yang menolak jenis gula-gula baru ke dalam array yang sudah wujud dalam fungsi tersebut. Jika anda menjalankan kod ini, array candy
akan tidak ditakrifkan, kerana ia adalah pembolehubah tempatan (tempatan kepada penutupan).
✅ Bagaimana anda boleh membuat array candy
boleh diakses? Cuba pindahkan ia ke luar penutupan. Dengan cara ini, array menjadi global, bukannya hanya tersedia dalam skop tempatan penutupan.
Tugasan
Di bawah deklarasi elemen dalam script.js
, buat fungsi:
function dragElement(terrariumElement) {
//set 4 positions for positioning on the screen
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
terrariumElement.onpointerdown = pointerDrag;
}
dragElement
mendapatkan objek terrariumElement
daripada deklarasi di bahagian atas skrip. Kemudian, anda menetapkan beberapa kedudukan tempatan pada 0
untuk objek yang dihantar ke fungsi. Ini adalah pembolehubah tempatan yang akan dimanipulasi untuk setiap elemen semasa anda menambah fungsi seret dan lepaskan dalam penutupan kepada setiap elemen. Terrarium akan dipenuhi oleh elemen-elemen yang diseret ini, jadi aplikasi perlu menjejaki di mana ia diletakkan.
Selain itu, terrariumElement
yang dihantar kepada fungsi ini diberikan acara pointerdown
, yang merupakan sebahagian daripada web APIs yang direka untuk membantu pengurusan DOM. onpointerdown
dicetuskan apabila butang ditekan, atau dalam kes kita, elemen yang boleh diseret disentuh. Pengendali acara ini berfungsi pada kedua-dua pelayar web dan mudah alih, dengan beberapa pengecualian.
✅ Pengendali acara onclick
mempunyai sokongan yang lebih luas merentas pelayar; mengapa anda tidak menggunakannya di sini? Fikirkan tentang jenis interaksi skrin yang tepat yang anda cuba cipta di sini.
Fungsi Pointerdrag
terrariumElement
kini sedia untuk diseret; apabila acara onpointerdown
dicetuskan, fungsi pointerDrag
dipanggil. Tambahkan fungsi tersebut tepat di bawah baris ini: terrariumElement.onpointerdown = pointerDrag;
:
Tugasan
function pointerDrag(e) {
e.preventDefault();
console.log(e);
pos3 = e.clientX;
pos4 = e.clientY;
}
Beberapa perkara berlaku. Pertama, anda menghalang acara lalai yang biasanya berlaku pada pointerdown
daripada berlaku dengan menggunakan e.preventDefault();
. Dengan cara ini anda mempunyai lebih kawalan ke atas tingkah laku antara muka.
Kembali ke baris ini apabila anda telah membina fail skrip sepenuhnya dan cuba tanpa
e.preventDefault()
- apa yang berlaku?
Kedua, buka index.html
dalam tetingkap pelayar, dan periksa antara muka. Apabila anda mengklik tumbuhan, anda boleh melihat bagaimana acara 'e' ditangkap. Selidiki acara tersebut untuk melihat berapa banyak maklumat yang dikumpulkan oleh satu acara pointerdown
!
Seterusnya, perhatikan bagaimana pembolehubah tempatan pos3
dan pos4
ditetapkan kepada e.clientX. Anda boleh mencari nilai e
dalam panel pemeriksaan. Nilai-nilai ini menangkap koordinat x dan y tumbuhan pada saat anda mengklik atau menyentuhnya. Anda memerlukan kawalan yang terperinci terhadap tingkah laku tumbuhan semasa anda mengklik dan menyeretnya, jadi anda menjejaki koordinat mereka.
✅ Adakah semakin jelas mengapa keseluruhan aplikasi ini dibina dengan satu penutupan besar? Jika tidak, bagaimana anda akan mengekalkan skop untuk setiap 14 tumbuhan yang boleh diseret?
Lengkapkan fungsi awal dengan menambah dua lagi manipulasi acara penunjuk di bawah pos4 = e.clientY
:
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
Sekarang anda menunjukkan bahawa anda mahu tumbuhan diseret bersama penunjuk semasa anda menggerakkannya, dan untuk gerakan seretan berhenti apabila anda tidak lagi memilih tumbuhan. onpointermove
dan onpointerup
adalah sebahagian daripada API yang sama seperti onpointerdown
. Antara muka akan membuang ralat sekarang kerana anda belum lagi mentakrifkan fungsi elementDrag
dan stopElementDrag
, jadi bina fungsi tersebut seterusnya.
Fungsi elementDrag dan stopElementDrag
Anda akan melengkapkan penutupan anda dengan menambah dua lagi fungsi dalaman yang akan mengendalikan apa yang berlaku apabila anda menyeret tumbuhan dan berhenti menyeretnya. Tingkah laku yang anda mahukan ialah anda boleh menyeret mana-mana tumbuhan pada bila-bila masa dan meletakkannya di mana-mana sahaja di skrin. Antara muka ini agak fleksibel (tiada zon lepasan, contohnya) untuk membolehkan anda mereka bentuk terrarium anda seperti yang anda suka dengan menambah, mengalih keluar, dan menyusun semula tumbuhan.
Tugasan
Tambahkan fungsi elementDrag
tepat selepas kurungan penutup pointerDrag
:
function elementDrag(e) {
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
console.log(pos1, pos2, pos3, pos4);
terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px';
terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px';
}
Dalam fungsi ini, anda melakukan banyak pengeditan pada kedudukan awal 1-4 yang anda tetapkan sebagai pembolehubah tempatan dalam fungsi luaran. Apa yang sedang berlaku di sini?
Semasa anda menyeret, anda menetapkan semula pos1
dengan menjadikannya sama dengan pos3
(yang anda tetapkan sebelum ini sebagai e.clientX
) tolak nilai e.clientX
semasa. Anda melakukan operasi yang sama pada pos2
. Kemudian, anda menetapkan semula pos3
dan pos4
kepada koordinat X dan Y baru elemen tersebut. Anda boleh melihat perubahan ini dalam konsol semasa anda menyeret. Kemudian, anda memanipulasi gaya CSS tumbuhan untuk menetapkan kedudukannya yang baru berdasarkan kedudukan baru pos1
dan pos2
, mengira koordinat X dan Y atas dan kiri tumbuhan berdasarkan perbandingan offsetnya dengan kedudukan baru ini.
offsetTop
danoffsetLeft
adalah sifat CSS yang menetapkan kedudukan elemen berdasarkan kedudukan elemen induknya; elemen induknya boleh menjadi mana-mana elemen yang tidak diposisikan sebagaistatic
.
Semua pengiraan semula kedudukan ini membolehkan anda menyesuaikan tingkah laku terrarium dan tumbuhannya.
Tugasan
Tugasan terakhir untuk melengkapkan antara muka ialah menambah fungsi stopElementDrag
selepas kurungan penutup elementDrag
:
function stopElementDrag() {
document.onpointerup = null;
document.onpointermove = null;
}
Fungsi kecil ini menetapkan semula acara onpointerup
dan onpointermove
supaya anda boleh sama ada memulakan semula kemajuan tumbuhan anda dengan mula menyeretnya semula, atau mula menyeret tumbuhan baru.
✅ Apa yang berlaku jika anda tidak menetapkan acara ini kepada null?
Sekarang anda telah melengkapkan projek anda!
🥇Tahniah! Anda telah menyiapkan terrarium anda yang cantik.
🚀Cabaran
Tambahkan pengendali acara baru kepada penutupan anda untuk melakukan sesuatu yang lebih kepada tumbuhan; contohnya, klik dua kali pada tumbuhan untuk membawanya ke hadapan. Jadilah kreatif!
Kuiz Pasca-Kuliah
Kajian Semula & Kajian Kendiri
Walaupun menyeret elemen di sekitar skrin kelihatan remeh, terdapat banyak cara untuk melakukannya dan banyak perangkap, bergantung pada kesan yang anda cari. Malah, terdapat keseluruhan API seret dan lepaskan yang boleh anda cuba. Kami tidak menggunakannya dalam modul ini kerana kesan yang kami mahukan agak berbeza, tetapi cuba API ini pada projek anda sendiri dan lihat apa yang anda boleh capai.
Cari lebih banyak maklumat tentang acara penunjuk di dokumen W3C dan di dokumen web MDN.
Sentiasa periksa keupayaan pelayar menggunakan CanIUse.com.
Tugasan
Bekerja lebih lanjut dengan DOM
Penafian:
Dokumen ini telah diterjemahkan menggunakan perkhidmatan terjemahan AI Co-op Translator. Walaupun kami berusaha untuk memastikan ketepatan, sila ambil maklum bahawa terjemahan automatik mungkin mengandungi kesilapan atau ketidaktepatan. Dokumen asal dalam bahasa asalnya harus dianggap sebagai sumber yang berwibawa. Untuk maklumat yang kritikal, terjemahan manusia profesional adalah disyorkan. Kami tidak bertanggungjawab atas sebarang salah faham atau salah tafsir yang timbul daripada penggunaan terjemahan ini.