Layar sentuh tersedia di semakin banyak perangkat, dari ponsel hingga layar desktop. Aplikasi Anda harus merespons sentuhan mereka dengan cara yang intuitif dan indah.
Layar sentuh tersedia di semakin banyak perangkat, mulai dari ponsel hingga layar desktop. Saat pengguna memilih untuk berinteraksi dengan UI, aplikasi Anda harus merespons sentuhan mereka secara intuitif.
Merespons status elemen
Pernahkah Anda menyentuh atau mengklik elemen pada halaman web dan bertanya-tanya apakah situs tersebut benar-benar mendeteksinya?
Cukup mengubah warna elemen saat pengguna menyentuh atau berinteraksi dengan bagian UI untuk memberikan jaminan dasar bahwa situs Anda berfungsi. Hal ini tidak hanya mengurangi frustrasi, tetapi juga dapat memberikan nuansa yang cepat dan responsif.
Elemen DOM dapat mewarisi salah satu status berikut: default, fokus, arahkan kursor,
dan aktif. Untuk mengubah UI bagi setiap status ini, kita harus menerapkan gaya
ke class pseudo berikut :hover
, :focus
, dan :active
seperti yang ditunjukkan di bawah ini:
.btn {
background-color: #4285f4;
}
.btn:hover {
background-color: #296cdb;
}
.btn:focus {
background-color: #0f52c1;
/* The outline parameter suppresses the border
color / outline when focused */
outline: 0;
}
.btn:active {
background-color: #0039a8;
}
Pada sebagian besar browser seluler, status hover dan/atau focus akan diterapkan ke elemen setelah diketuk.
Pertimbangkan dengan cermat gaya yang Anda tetapkan dan tampilannya bagi pengguna setelah menyelesaikan sentuhan mereka.
Menyembunyikan gaya browser default
Setelah menambahkan gaya untuk status yang berbeda, Anda akan melihat bahwa sebagian besar browser
mengimplementasikan gaya mereka sendiri sebagai respons terhadap sentuhan pengguna. Hal ini sebagian besar
karena saat perangkat seluler pertama kali diluncurkan, sejumlah situs tidak
memiliki gaya untuk status :active
. Akibatnya, banyak browser menambahkan
warna atau gaya sorotan tambahan untuk memberikan masukan kepada pengguna.
Sebagian besar browser menggunakan properti CSS outline
untuk menampilkan lingkaran di sekitar
elemen saat elemen difokuskan. Anda dapat menyembunyikannya dengan:
.btn:focus {
outline: 0;
/* Add replacement focus styling here (i.e. border) */
}
Safari dan Chrome menambahkan warna sorotan ketuk yang dapat dicegah dengan
properti CSS -webkit-tap-highlight-color
:
/* Webkit / Chrome Specific CSS to remove tap
highlight color */
.btn {
-webkit-tap-highlight-color: transparent;
}
Internet Explorer di Windows Phone memiliki perilaku yang serupa, tetapi disembunyikan melalui tag meta:
<meta name="msapplication-tap-highlight" content="no">
Firefox memiliki dua efek samping yang perlu ditangani.
Class pseudo -moz-focus-inner
, yang menambahkan outline pada
elemen yang dapat disentuh, dapat Anda hapus dengan menetapkan border: 0
.
Jika menggunakan elemen <button>
di Firefox, Anda akan mendapatkan penerapan gradien, yang dapat dihapus dengan menyetel background-image: none
.
/* Firefox Specific CSS to remove button
differences and focus ring */
.btn {
background-image: none;
}
.btn::-moz-focus-inner {
border: 0;
}
Menonaktifkan pilihan pengguna
Saat membuat UI, mungkin ada beberapa skenario ketika Anda ingin pengguna berinteraksi dengan elemen, tetapi Anda ingin menyembunyikan perilaku default pemilihan teks dengan menekan lama atau menarik mouse ke atas UI Anda.
Anda dapat melakukannya dengan properti CSS user-select
, tetapi berhati-hatilah karena
melakukan hal ini pada konten dapat extremely menjengkelkan
bagi pengguna jika mereka ingin memilih teks dalam elemen.
Jadi, pastikan Anda menggunakannya dengan hati-hati dan hemat.
/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
user-select: none;
}
Mengimplementasikan gestur khusus
Jika Anda memiliki ide untuk interaksi dan gestur kustom untuk situs Anda, ada dua topik yang perlu diingat:
- Cara mendukung semua browser.
- Cara menjaga kecepatan frame tetap tinggi.
Dalam artikel ini, kita akan melihat topik yang membahas API yang perlu kita dukung untuk membuka semua browser lalu membahas cara menggunakan peristiwa ini secara efisien.
Bergantung pada gestur yang diinginkan, Anda mungkin ingin pengguna berinteraksi dengan satu elemen pada satu waktu atau Anda ingin mereka dapat berinteraksi dengan beberapa elemen secara bersamaan.
Kita akan melihat dua contoh dalam artikel ini, yang menunjukkan dukungan untuk semua browser dan cara menjaga kecepatan frame tetap tinggi.
Contoh pertama akan memungkinkan pengguna berinteraksi dengan satu elemen. Dalam hal ini, Anda mungkin ingin semua peristiwa sentuh diberikan ke satu elemen tersebut, selama gestur tersebut pertama kali dimulai pada elemen itu sendiri. Misalnya, memindahkan jari dari elemen yang dapat digeser masih dapat mengontrol elemen tersebut.
Cara ini berguna karena memberikan banyak fleksibilitas bagi pengguna, tetapi menerapkan pembatasan tentang cara pengguna dapat berinteraksi dengan UI Anda.
Namun, jika Anda mengharapkan pengguna berinteraksi dengan beberapa elemen pada saat yang sama (menggunakan multi-sentuh), Anda harus membatasi sentuhan ke elemen tertentu.
Cara ini lebih fleksibel bagi pengguna, tetapi mempersulit logika untuk memanipulasi UI dan kurang tahan terhadap error pengguna.
Menambahkan pemroses peristiwa
Di Chrome (versi 55 dan yang lebih baru), Internet Explorer & Edge,
PointerEvents
adalah pendekatan yang direkomendasikan untuk mengimplementasikan gestur kustom.
Di browser lain, TouchEvents
dan MouseEvents
adalah pendekatan yang tepat.
Fitur hebat PointerEvents
adalah menggabungkan beberapa jenis input,
termasuk peristiwa mouse, sentuh, dan pena, ke dalam satu kumpulan
callback. Peristiwa yang akan diproses adalah pointerdown
, pointermove
, pointerup
, dan pointercancel
.
Yang setara di browser lain adalah touchstart
, touchmove
,
touchend
, dan touchcancel
untuk peristiwa sentuh, dan jika ingin menerapkan
gestur yang sama untuk input mouse, Anda harus mengimplementasikan mousedown
,
mousemove
, dan mouseup
.
Jika ada pertanyaan tentang peristiwa mana yang akan digunakan, lihat tabel Peristiwa sentuh, mouse, dan pointer ini.
Penggunaan peristiwa ini mengharuskan pemanggilan metode addEventListener()
pada elemen DOM, beserta nama peristiwa, fungsi callback, dan boolean.
Boolean menentukan apakah Anda harus menangkap peristiwa sebelum atau setelah elemen lain memiliki kesempatan untuk menangkap dan menafsirkan peristiwa. (true
berarti Anda menginginkan peristiwa sebelum elemen lainnya.)
Berikut adalah contoh mendengarkan untuk memulai interaksi.
// Check if pointer events are supported.
if (window.PointerEvent) {
// Add Pointer Event Listener
swipeFrontElement.addEventListener('pointerdown', this.handleGestureStart, true);
swipeFrontElement.addEventListener('pointermove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('pointerup', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('pointercancel', this.handleGestureEnd, true);
} else {
// Add Touch Listener
swipeFrontElement.addEventListener('touchstart', this.handleGestureStart, true);
swipeFrontElement.addEventListener('touchmove', this.handleGestureMove, true);
swipeFrontElement.addEventListener('touchend', this.handleGestureEnd, true);
swipeFrontElement.addEventListener('touchcancel', this.handleGestureEnd, true);
// Add Mouse Listener
swipeFrontElement.addEventListener('mousedown', this.handleGestureStart, true);
}
Menangani interaksi elemen tunggal
Dalam cuplikan kode singkat di atas, kami hanya menambahkan pemroses peristiwa awal untuk peristiwa mouse. Alasannya adalah peristiwa mouse hanya akan dipicu saat kursor diarahkan ke atas elemen yang ditambahi pemroses peristiwa.
TouchEvents
akan melacak gestur setelah dimulai terlepas dari mana pun
sentuhan terjadi dan PointerEvents
akan melacak peristiwa di mana pun terjadinya sentuhan
setelah kita memanggil setPointerCapture
pada elemen DOM.
Untuk gerakan mouse dan peristiwa akhir, kita menambahkan pemroses peristiwa di metode awal gestur dan menambahkan pemroses ke dokumen, sehingga pemroses tersebut dapat melacak kursor hingga gestur selesai.
Langkah-langkah yang diambil untuk menerapkannya adalah:
- Menambahkan semua pemroses TouchEvent dan PointerEvent. Untuk MouseEvents, tambahkan hanya peristiwa awal.
- Di dalam callback gestur awal, ikat gerakan mouse dan peristiwa akhir ke
dokumen. Dengan cara ini, semua peristiwa mouse akan diterima terlepas dari apakah
peristiwa tersebut terjadi pada elemen asli atau tidak. Untuk PointerEvents, kita perlu memanggil
setPointerCapture()
pada elemen asli untuk menerima semua peristiwa lebih lanjut. Kemudian tangani awal gestur. - Menangani peristiwa pemindahan.
- Pada peristiwa akhir, hapus gerakan mouse dan pemroses akhir dari dokumen, lalu akhiri gestur.
Berikut adalah cuplikan metode handleGestureStart()
yang menambahkan peristiwa pemindahan dan peristiwa akhir ke dokumen:
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if(evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
Callback akhir yang kita tambahkan adalah handleGestureEnd()
, yang menghapus pemroses peristiwa gerak dan peristiwa akhir dari dokumen serta melepaskan rekaman pointer saat gestur selesai seperti ini:
// Handle end gestures
this.handleGestureEnd = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 0) {
return;
}
rafPending = false;
// Remove Event Listeners
if (window.PointerEvent) {
evt.target.releasePointerCapture(evt.pointerId);
} else {
// Remove Mouse Listeners
document.removeEventListener('mousemove', this.handleGestureMove, true);
document.removeEventListener('mouseup', this.handleGestureEnd, true);
}
updateSwipeRestPosition();
initialTouchPos = null;
}.bind(this);
Dengan mengikuti pola penambahan peristiwa pemindahan ke dokumen ini, jika pengguna mulai berinteraksi dengan elemen dan menggerakkan gestur di luar elemen, kita akan terus mendapatkan gerakan mouse terlepas dari posisinya di halaman karena peristiwa tersebut diterima dari dokumen.
Diagram ini menunjukkan fungsi peristiwa sentuh saat kita menambahkan peristiwa pindahkan dan akhiri ke dokumen setelah gestur dimulai.
Merespons sentuhan secara efisien
Setelah menangani peristiwa awal dan akhir, kita dapat benar-benar merespons peristiwa sentuh.
Untuk peristiwa awal dan pemindahan, Anda dapat dengan mudah mengekstrak x
dan y
dari peristiwa.
Contoh berikut memeriksa apakah peristiwa berasal dari TouchEvent
dengan
memeriksa apakah targetTouches
ada atau tidak. Jika ya, kode akan mengekstrak
clientX
dan clientY
dari sentuhan pertama.
Jika peristiwa berupa PointerEvent
atau MouseEvent
, peristiwa akan mengekstrak clientX
dan
clientY
langsung dari peristiwa itu sendiri.
function getGesturePointFromEvent(evt) {
var point = {};
if (evt.targetTouches) {
// Prefer Touch Events
point.x = evt.targetTouches[0].clientX;
point.y = evt.targetTouches[0].clientY;
} else {
// Either Mouse event or Pointer Event
point.x = evt.clientX;
point.y = evt.clientY;
}
return point;
}
TouchEvent
memiliki tiga daftar yang berisi data sentuh:
touches
: daftar semua sentuhan saat ini di layar, terlepas dari elemen DOM tempatnya berada.targetTouches
: daftar sentuhan yang saat ini terikat pada elemen DOM yang dikaitkan oleh peristiwa.changedTouches
: daftar sentuhan yang berubah sehingga menghasilkan peristiwa diaktifkan.
Dalam kebanyakan kasus, targetTouches
memberikan semua yang Anda butuhkan dan inginkan. (Untuk
mengetahui informasi selengkapnya tentang daftar ini, lihat Daftar sentuh).
Menggunakan requestAnimationFrame
Karena callback peristiwa diaktifkan pada thread utama, kita ingin menjalankan kode sesedikit mungkin dalam callback untuk peristiwa, sehingga menjaga kecepatan frame tetap tinggi dan mencegah jank.
Dengan requestAnimationFrame()
, kita memiliki kesempatan untuk mengupdate UI tepat sebelum browser bermaksud menggambar frame dan akan membantu kita mengeluarkan beberapa pekerjaan dari callback peristiwa.
Jika tidak terbiasa dengan requestAnimationFrame()
, Anda
dapat mempelajari lebih lanjut di sini.
Implementasi standar adalah menyimpan koordinat x
dan y
dari
peristiwa awal dan memindahkan, serta meminta frame animasi di dalam callback peristiwa
pemindah.
Dalam demo, kami menyimpan posisi sentuh awal di handleGestureStart()
(cari initialTouchPos
):
// Handle the start of gestures
this.handleGestureStart = function(evt) {
evt.preventDefault();
if (evt.touches && evt.touches.length > 1) {
return;
}
// Add the move and end listeners
if (window.PointerEvent) {
evt.target.setPointerCapture(evt.pointerId);
} else {
// Add Mouse Listeners
document.addEventListener('mousemove', this.handleGestureMove, true);
document.addEventListener('mouseup', this.handleGestureEnd, true);
}
initialTouchPos = getGesturePointFromEvent(evt);
swipeFrontElement.style.transition = 'initial';
}.bind(this);
Metode handleGestureMove()
menyimpan posisi peristiwanya sebelum meminta frame animasi jika perlu, dengan meneruskan fungsi onAnimFrame()
sebagai callback:
this.handleGestureMove = function (evt) {
evt.preventDefault();
if (!initialTouchPos) {
return;
}
lastTouchPos = getGesturePointFromEvent(evt);
if (rafPending) {
return;
}
rafPending = true;
window.requestAnimFrame(onAnimFrame);
}.bind(this);
Nilai onAnimFrame
adalah fungsi yang saat dipanggil, akan mengubah UI
untuk memindahkannya. Dengan meneruskan fungsi ini ke requestAnimationFrame()
, kita memberi tahu browser untuk memanggilnya tepat sebelum memperbarui halaman (yaitu, menampilkan perubahan apa pun ke halaman).
Dalam callback handleGestureMove()
, kami awalnya memeriksa apakah rafPending
bernilai salah (false),
yang menunjukkan apakah onAnimFrame()
telah dipanggil oleh requestAnimationFrame()
sejak peristiwa pemindahan terakhir. Artinya, kita hanya memiliki satu requestAnimationFrame()
yang menunggu dijalankan pada satu waktu.
Saat callback onAnimFrame()
dieksekusi, kita menetapkan transformasi pada elemen apa pun yang ingin dipindahkan sebelum memperbarui rafPending
ke false
, sehingga peristiwa sentuh berikutnya meminta frame animasi baru.
function onAnimFrame() {
if (!rafPending) {
return;
}
var differenceInX = initialTouchPos.x - lastTouchPos.x;
var newXTransform = (currentXPosition - differenceInX)+'px';
var transformStyle = 'translateX('+newXTransform+')';
swipeFrontElement.style.webkitTransform = transformStyle;
swipeFrontElement.style.MozTransform = transformStyle;
swipeFrontElement.style.msTransform = transformStyle;
swipeFrontElement.style.transform = transformStyle;
rafPending = false;
}
Mengontrol gestur menggunakan tindakan sentuh
Properti CSS touch-action
memungkinkan Anda mengontrol perilaku sentuh default
sebuah elemen. Dalam contoh ini, kita menggunakan touch-action: none
untuk mencegah browser melakukan apa pun dengan sentuhan pengguna, sehingga kita dapat mencegat semua peristiwa sentuh.
/* Pass all touches to javascript: */
button.custom-touch-logic {
touch-action: none;
}
Menggunakan touch-action: none
terbilang opsi ekstrem karena mencegah semua
perilaku browser default. Dalam banyak kasus, salah satu opsi di bawah ini merupakan solusi yang lebih baik.
touch-action
memungkinkan Anda menonaktifkan gestur yang diimplementasikan oleh browser.
Misalnya, IE10+ mendukung gestur ketuk dua kali untuk zoom. Dengan menyetel
touch-action
dari manipulation
, Anda akan mencegah perilaku ketuk dua kali
default.
Hal ini memungkinkan Anda untuk menerapkan gestur ketuk dua kali sendiri.
Berikut adalah daftar nilai touch-action
yang umum digunakan:
Mendukung IE versi lama
Jika ingin mendukung IE10, Anda harus menangani versi yang diawali vendor dari
PointerEvents
.
Untuk memeriksa dukungan PointerEvents
, Anda biasanya akan mencari
window.PointerEvent
, tetapi dalam IE10, Anda akan mencari
window.navigator.msPointerEnabled
.
Nama peristiwa dengan awalan vendor adalah: 'MSPointerDown'
, 'MSPointerUp'
, dan 'MSPointerMove'
.
Contoh di bawah ini menunjukkan cara memeriksa dukungan dan mengganti nama peristiwa.
var pointerDownName = 'pointerdown';
var pointerUpName = 'pointerup';
var pointerMoveName = 'pointermove';
if (window.navigator.msPointerEnabled) {
pointerDownName = 'MSPointerDown';
pointerUpName = 'MSPointerUp';
pointerMoveName = 'MSPointerMove';
}
// Simple way to check if some form of pointerevents is enabled or not
window.PointerEventsSupport = false;
if (window.PointerEvent || window.navigator.msPointerEnabled) {
window.PointerEventsSupport = true;
}
Untuk mengetahui informasi selengkapnya, baca artikel pembaruan dari Microsoft ini.
Referensi
Class pseudo untuk status sentuh
Referensi peristiwa sentuh definitif dapat ditemukan di sini: Peristiwa Sentuh W3C.
Peristiwa sentuh, mouse, dan pointer
Peristiwa ini adalah elemen penyusun untuk menambahkan gestur baru ke aplikasi Anda:
Daftar sentuh
Setiap peristiwa sentuh menyertakan tiga atribut daftar:
Mengaktifkan dukungan status aktif di iOS
Sayangnya, Safari di iOS tidak menerapkan status active secara default. Untuk membuatnya berfungsi, Anda perlu menambahkan pemroses peristiwa touchstart
ke isi dokumen atau ke setiap elemen.
Anda harus melakukannya di belakang pengujian agen pengguna sehingga hanya berjalan di perangkat iOS.
Menambahkan touch start ke isi memiliki keuntungan karena diterapkan ke semua elemen dalam DOM, tetapi hal ini mungkin menyebabkan masalah performa saat men-scroll halaman.
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
document.body.addEventListener('touchstart', function() {}, false);
}
};
Alternatifnya adalah dengan menambahkan pemroses sentuhan mulai ke semua elemen yang dapat berinteraksi di halaman, sehingga mengurangi beberapa masalah performa.
window.onload = function() {
if (/iP(hone|ad)/.test(window.navigator.userAgent)) {
var elements = document.querySelectorAll('button');
var emptyFunction = function() {};
for (var i = 0; i < elements.length; i++) {
elements[i].addEventListener('touchstart', emptyFunction, false);
}
}
};