Memakai Fasilitas Drag and Drop Di HTML5


Pada artikel ini, saya akan menggunakan fasilitas drag and drop dari HTML5 untuk membuat sebuah permainan image puzzle sederhana.

Langkah pertama yang saya lakukan adalah membuat HTML yang memecah gambar menjadi 36 <div> berbeda. Nantinya setiap <div> ini harus bisa di-drag. Untuk itu, saya membuat HTML seperti berikut ini:

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'/>
    <style type="text/css">
        .kotak {
            background: url('Koala.jpg') no-repeat;
            position: absolute; border: 2px solid black;
        }
        #petunjuk {
            position: absolute; top: 0px; left: 0px;    
        }
    </style>
    <title>Latihan Drag And Drop</title>
</head>
<body>
    <div id='petunjuk'>
        <img id='gambar' src='Koala.jpg' style='opacity: 0.3;'/>
    </div>    
    <div id='drag'></div>
    <script type="text/javascript">
        var gambar=document.getElementById('gambar');
        var drag=document.getElementById('drag');
        var widthKotak=gambar.width / 6
        var heightKotak=gambar.height / 6;
        var x=0, y=0, posisi=0;
        for (var baris=0; baris<6; baris++) {
            for (var kolom=0; kolom<6; kolom++) {
                var kotak = document.createElement('div');
                kotak.id = posisi++;
                kotak.className = 'kotak';                
                kotak.style.width = widthKotak + 'px';
                kotak.style.height = heightKotak + 'px';                                                                                  
                kotak.style.top = y + 'px';
                kotak.style.left = x +'px';
                kotak.style.backgroundPosition = -x + 'px ' + -y + 'px';                
                kotak.draggable = 'true';             
                kotak.ondragstart = function(e) {
                    e.dataTransfer.setData('text', e.target.id);
                }                           
                drag.appendChild(kotak);
                x+=widthKotak;
            }
            x = 0;
            y+=heightKotak;
        }
    </script>
</body>
</html>

Pada HTML di atas, terdapat sebuah <div> bernama petunjuk yang didalamnya berisi gambar transparan. Saya kemudian membuat 36 <div> dengan class kotak di atas gambar transparan tersebut dimana masing-masing kotak mewakili potongan dari gambar. Untuk hanya menampilkan bagian tertentu dari gambar, saya menggunakan CSS background-position.

Pada Firefox, agar setiap kotak dapat di-drag, saya perlu mengatur atribut draggable menjadi "true". Selain itu, saya wajib memanggil DataTransfer.setData() pada event handler untuk dragstart. Tujuannya adalah untuk memberikan nilai yang diwakili oleh elemen yang sedang di-drag. Pada kode program di atas, nilai yang diwakili masing-masing kotak adalah id-nya yang berupa nomor posisi (dari 0 hingga 35).

Bila saya menampilkan HTML di atas pada browser, setiap kotak sudah dapat di-drag seperti yang terlihat pada gambar berikut ini:

Membuat kotak (<div>) secara dinamis

Membuat kotak (<div>) secara dinamis

Agar lebih menarik, saya perlu membuat posisi masing-masing kotak menjadi acak dengan mengubah kode program yang mengatur atribut CSS top dan left menjadi seperti berikut ini:

kotak.style.top = Math.random()*(window.innerHeight-heightKotak) + 'px';
kotak.style.left = Math.random()*(window.innerWidth-widthKotak) +'px';

Sekarang, bila saya menampilkan halaman HTML di browser, saya akan memperoleh tampilan yang acak seperti berikut ini:

Mengacak posisi kotak (<div>) yang telah dibuat

Mengacak posisi kotak (<div>) yang telah dibuat

Sebuah permasalahan yang muncul adalah ketika saya men-drag kotak ke sebuah posisi berbeda, ia tidak akan berubah dan tetap diam di tempat. Akan lebih baik bila kotak yang di-drag akan berpindah ke posisi terakhir dimana pengguna melepas mouse-nya (baik itu di tujuan maupun di tempat kosong). Oleh sebab itu, saya akan mengubah HTML di atas menjadi seperti berikut ini:

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'/>
    <style type="text/css">
        .kotak {
            background: url('Koala.jpg') no-repeat;
            position: absolute; border: 2px solid black;
        }
        #petunjuk {
            position: absolute; top: 0px; left: 0px;    
        }
    </style>
    <title>Latihan Drag And Drop</title>
</head>
<body>
    <div id='petunjuk'>
        <img id='gambar' src='Koala.jpg' style='opacity: 0.3;'/>
    </div>    
    <div id='drag' draggable='false'></div>
    <script type="text/javascript">
        var gambar=document.getElementById('gambar');
        var drag=document.getElementById('drag');
        var widthKotak=gambar.width / 6
        var heightKotak=gambar.height / 6;
        var x=0, y=0, posisi=0;         
        var startScreenX=0,startScreenY=0;      

        drag.addEventListener('dragstart', function(e) {
            if (e.target.className=='kotak') {
                e.dataTransfer.setData('text', e.target.id);
                startScreenX = e.screenX;
                startScreenY = e.screenY;               
            }       
        }, true);
        drag.addEventListener('dragend', function(e) {
            var target = e.target;
            if (target.className=='kotak') {
                target.style.top = parseInt(target.style.top) + (e.screenY-startScreenY) + "px";
                target.style.left = parseInt(target.style.left) + (e.screenX-startScreenX) + "px";
            }
        }, true);

        for (var baris=0; baris<6; baris++) {
            for (var kolom=0; kolom<6; kolom++) {
                var kotak = document.createElement('div');
                kotak.id = posisi++;
                kotak.className = 'kotak';                
                kotak.style.width = widthKotak + 'px';
                kotak.style.height = heightKotak + 'px';                                                                                  
                kotak.style.top = Math.random()*(window.innerHeight-heightKotak) + 'px';
                kotak.style.left = Math.random()*(window.innerWidth-widthKotak) +'px';
                kotak.style.backgroundPosition = -x + 'px ' + -y + 'px';                
                kotak.draggable = 'true';                             
                drag.appendChild(kotak);
                x+=widthKotak;
            }
            x = 0;
            y+=heightKotak;
        }
    </script>
</body>
</html>

Salah satu perubahan yang saya lakukan di atas adalah saya tidak lagi memberikan event handler pada masing-masing kotak, melainkan hanya memberikan satu event handler pada drag. Tujuannya adalah untuk meningkatkan kinerja (berbeda dengan Java atau C# dimana method adalah sebuah operasi yang sama per class, pada JavaScript, masing-masing function adalah nilai yang perlu ditampung di memori!) Kode program masih dapat bekerja karena event pada JavaScript memiliki event bubling dimana event untuk sebuah elemen akan diteruskan ke parent-nya, grandparent-nya dan seterusnya. Karena seluruh kotak berada dalam drag, maka saya cukup memberikan satu event handler pada drag untuk mewakili seluruh kotak yang ada.

Saya menambahkan event handler untuk dragend yang akan dikerjakan pada saat proses drag sudah selesai. Pada event handler ini, saya menghitung selisih piksel (berdasarkan nilai Event.screenX dan Event.screenY lalu mengatur posisi kotak yang baru (melalui atribut CSS top dan left) berdasarkan selisih tersebut.

Berikutnya, saya perlu membuat target untuk di-drop. Untuk itu, saya membuat <div> baru untuk mewakili target drop sehingga isi HTML saya terlihat seperti berikut ini:

...
<body>
    <div id='petunjuk'>
        <img id='gambar' src='Koala.jpg' style='opacity: 0.3;'/>
    </div>    
    <div id='target'></div>   
    <div id='drag' draggable='false'></div>
</body>
...

Kemudian, saya membuat <div> baru untuk setiap kotak yang saya isi pada target dengan menggunakan JavaScript seperti berikut ini:

...
for (var baris=0; baris<3; baris++) {
    for (var kolom=0; kolom<3; kolom++) {
        // Kotak berisi gambar
        var kotak = document.createElement('div');
        kotak.id = posisi;
        ...

        // Kotak sebagai target
        var kotakTarget = document.createElement('div');
        kotakTarget.id = posisi;
        kotakTarget.className = 'target';
        kotakTarget.style.width = widthKotak + 'px';
        kotakTarget.style.height = heightKotak + 'px';
        kotakTarget.style.top = y + 'px';
        kotakTarget.style.left = x + 'px';
        target.appendChild(kotakTarget);

        x+=widthKotak;
        posisi++;
    }
    x = 0;
    y+=heightKotak;
}
...

Berikutnya, saya perlu menambahkan event handler pada dragenter dan dragover sehingga ia mengerjakan e.preventDefault(). Dengan demikian event handler default pada browser akan dibatalkan (karena pada defaultnya, <div> tidak dapat menjadi target drop). Oleh sebab itu, saya menambahkan JavaScript berikut ini:

var preventer = function(e) { e.preventDefault() };
target.addEventListener('dragenter', preventer, true);  
target.addEventListener('dragover', preventer, true);  

Bila pengguna men-drop pada salah satu kotak pada target, maka akan terjadi event drop di target. Saya perlu menambahkan event handler tersebut seperti berikut ini:

target.addEventListener('drop', function(e) {         
    var diterima = e.dataTransfer.getData('text');
    var diharapkan = e.originalTarget.id;
    if (diterima == diharapkan) {                               
        // Tambahkan nanti!                     
    }
    e.preventDefault();
}, true);       

Untuk menampilkan visualisasi bahwa target dapat menjadi target untuk drop, saya akan menggunakan CSS pseudoclass -moz-drag-over yang hanya berjalan di Firefox. Pada CSS Selectors Level 4 yang saat ini belum stabil (bagian dari CSS4 nanti), ini mungkin setara dengan pseudoclass :active-drop-target. Dengan demikian, deklarasi CSS akan terlihat seperti pada gambar berikut ini:

.target {
    position: absolute; 
}
.target:-moz-drag-over {
    background-color: green; opacity: 0.5;
}

Sekarang, bila saya menampilkan HTML di browser, saya akan memperoleh tampilan seperti berikut ini saat men-drag ke salah satu target:

Memberikan clue pada target drop (<div>) untuk kotak

Memberikan clue pada target drop (<div>) untuk kotak

Berikutnya, saya perlu menentukan apa yang akan terjadi bila pengguna men-drop kotak ke target. Untuk itu saya perlu mengubah kode program di event handler untuk drop. Bukankah saya sudah pernah memberikan handler pada event dragend, lalu apa bedanya dengan drag? Event dragend akan tetap dikerjakan bila drag dibatalkan oleh pengguna. Selain itu, event dragend berlaku untuk elemen yang sedang di-drag. Saya menggunakannya untuk memindahkan kotak tidak peduli apapun tujuannya. Sebaliknya, event drop berlaku pada element yang menjadi tujuan drop (dalam hal ini adalah target).

Perubahan yang saya lakukan menyebabkan HTML saya menjadi seperti berikut ini:

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'/>
    <style type="text/css">
        .kotak {
            background: url('Koala.jpg') no-repeat;
            position: absolute; border: 2px solid black;
        }
        .target {
            position: absolute; 
        }
        .target:-moz-drag-over {
            background-color: green; opacity: 0.5;
        }
        .benar {
            border: 2px solid green;
        }
        #petunjuk {
            position: absolute; top: 0px; left: 0px;    
        }
    </style>
    <title>Latihan Drag And Drop</title>
</head>
<body>
    <div id='petunjuk'>
        <img id='gambar' src='Koala.jpg' style='opacity: 0.3;'/>
    </div>    
    <div id='target'></div>   
    <div id='drag' draggable='false'></div>
    <script type="text/javascript">
        var gambar=document.getElementById('gambar');
        var drag=document.getElementById('drag');
        var target=document.getElementById('target');
        var widthKotak=gambar.width / 6;
        var heightKotak=gambar.height / 6;
        var x=0, y=0, posisi=0;         
        var startScreenX=0,startScreenY=0;      
        var lastHit = false;

        drag.addEventListener('dragstart', function(e) {
            if (e.target.className=='kotak') {
                e.dataTransfer.setData('text', e.target.id);
                startScreenX = e.screenX;
                startScreenY = e.screenY;               
            }       
        }, true);
        drag.addEventListener('dragend', function(e) {
            var target = e.target;
            if (target.className=='kotak' && !lastHit) {
                target.style.top = parseInt(target.style.top) + (e.screenY-startScreenY) + "px";
                target.style.left = parseInt(target.style.left) + (e.screenX-startScreenX) + "px";                
            }
            lastHit = false;
        }, true);


        target.addEventListener('drop', function(e) {         
            var diterima = e.dataTransfer.getData('text');
            var diharapkan = e.originalTarget.id;
            if (diterima == diharapkan) {                               
                var kotak = document.querySelector("div[id='" + diterima + "'].kotak");
                kotak.draggable = false;
                kotak.style.left = e.originalTarget.style.left;
                kotak.style.top = e.originalTarget.style.top;
                kotak.style.zIndex = -1;
                kotak.classList.add('benar');                                                                                                                     
                target.removeChild(e.originalTarget);
                lastHit = true;                     
            }
            e.preventDefault();
        }, true);       

        var preventer = function(e) { e.preventDefault() };
        target.addEventListener('dragenter', preventer, true);  
        target.addEventListener('dragover', preventer, true);                 

        for (var baris=0; baris<6; baris++) {
            for (var kolom=0; kolom<6; kolom++) {
                // Kotak berisi gambar
                var kotak = document.createElement('div');
                kotak.id = posisi;
                kotak.className = 'kotak';                
                kotak.style.width = widthKotak + 'px';
                kotak.style.height = heightKotak + 'px';                                                                                  
                kotak.style.top = Math.random()*(window.innerHeight-heightKotak) + 'px';
                kotak.style.left = Math.random()*(window.innerWidth-widthKotak) +'px';
                kotak.style.backgroundPosition = -x + 'px ' + -y + 'px';                
                kotak.draggable = 'true';                                         
                drag.appendChild(kotak);                

                // Kotak sebagai target
                var kotakTarget = document.createElement('div');
                kotakTarget.id = posisi;
                kotakTarget.className = 'target';
                kotakTarget.style.width = widthKotak + 'px';
                kotakTarget.style.height = heightKotak + 'px';
                kotakTarget.style.top = y + 'px';
                kotakTarget.style.left = x + 'px';
                target.appendChild(kotakTarget);

                x+=widthKotak;
                posisi++;
            }
            x = 0;
            y+=heightKotak;
        }
    </script>
</body>
</html>

Pada JavaScript di atas, saya memakai query selector seperti "div[id='" + diterima + "].kotak'" karena id masing-masing kotak dalam bentuk angka. HTML5 memungkinkan id berupa angka, tapi syntax CSS tidak memungkinkan id berupa angka seperti #07.kotak. Bila sebuah kotak di-drop ke target yang benar, maka saya membuatnya menjadi tidak dapat di-drag lagi dengan memberikan nilai atribut draggable=false. Selain itu, saya menambahkan sebuah CSS class baru padanya dengan perintah kotak.classList.add('benar') yang membuatnya memiliki border berwarna hijau. Bagi yang terbiasa dengan jQuery, ini mirip seperti $(kotak).addClass('benar').

Bila saya menjalankan HTML di atas pada browser, image puzzle sekarang dapat dimainkan, seperti yang terlihat pada gambar berikut ini:

Contoh hasil di browser

Contoh hasil di browser

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: