Layar sentuh tersedia pada semakin banyak perangkat, dari ponsel hingga layar desktop. Aplikasi Anda harus merespons setiap sentuhan secara intuitif dan indah.
Layar sentuh tersedia di semakin banyak perangkat, mulai dari ponsel hingga layar desktop. Saat pengguna memilih untuk berinteraksi dengan UI Anda, aplikasi harus merespons setiap sentuhan secara intuitif.
Merespons status elemen
Pernahkah Anda menyentuh atau mengklik elemen di halaman web dan bertanya-tanya apakah situs itu 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 kesan cepat dan responsif.
Elemen DOM dapat mewarisi salah satu status berikut: default, fokus, pengarahan kursor,
dan aktif. Untuk mengubah UI masing-masing status, kita perlu menerapkan gaya
ke class pseudo berikut :hover
, :focus
, dan :active
seperti yang ditampilkan 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;
}
Di sebagian besar browser seluler, status arahkan kursor dan/atau fokus akan diterapkan ke elemen setelah diketuk.
Pertimbangkan dengan hati-hati gaya yang Anda setel dan bagaimana gaya tersebut akan terlihat oleh pengguna setelah menyelesaikan sentuhan mereka.
Menyembunyikan gaya browser default
Setelah Anda menambahkan gaya untuk status yang berbeda, Anda akan melihat bahwa sebagian besar browser
mengimplementasikan gaya mereka sendiri sebagai respons terhadap sentuhan pengguna. Hal ini terutama
karena saat pertama kali perangkat seluler diluncurkan, sejumlah situs tidak
memiliki gaya visual 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 sekeliling elemen saat elemen difokuskan. Anda dapat meredamnya dengan:
.btn:focus {
outline: 0;
/* Add replacement focus styling here (i.e. border) */
}
Safari dan Chrome menambahkan warna sorot 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 pada Windows Phone memiliki perilaku yang serupa, tetapi diredam melalui tag meta:
<meta name="msapplication-tap-highlight" content="no">
Firefox memiliki dua efek samping.
Class semu -moz-focus-inner
, yang menambahkan garis batas pada
elemen yang dapat disentuh, yang dapat Anda hapus dengan menyetel border: 0
.
Jika menggunakan elemen <button>
di Firefox, Anda akan mendapatkan gradien yang diterapkan, yang dapat Anda hapus dengan menetapkan 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 skenario di mana Anda ingin pengguna berinteraksi dengan elemen, tetapi Anda ingin menyembunyikan perilaku default memilih teks dengan tekan lama atau menarik mouse di atas UI.
Anda dapat melakukannya dengan properti CSS user-select
, tetapi waspadalah bahwa
melakukan hal ini pada konten dapat sangat menyebalkan
bagi pengguna jika mereka ingin memilih teks dalam elemen.
Jadi, pastikan Anda menggunakannya dengan hati-hati dan seperlunya.
/* Example: Disable selecting text on a paragraph element: */
p.disable-text-selection {
user-select: none;
}
Mengimplementasikan gestur kustom
Jika Anda memiliki gagasan untuk interaksi dan gestur khusus 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 secara tepat topik yang mencakup API yang perlu kita dukung untuk membuka semua browser, lalu membahas cara menggunakan peristiwa ini secara efisien.
Bergantung pada isyarat yang ingin dilakukan, 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, keduanya menunjukkan dukungan untuk semua browser dan cara menjaga agar kecepatan frame tetap tinggi.
Contoh pertama memungkinkan pengguna untuk berinteraksi dengan satu elemen. Dalam hal ini, Anda mungkin ingin semua peristiwa sentuh diberikan ke satu elemen tersebut, selama gestur pertama kali dimulai pada elemen itu sendiri. Misalnya, memindahkan jari dari elemen yang dapat digeser masih dapat mengontrol elemen.
Hal ini berguna karena memberikan banyak fleksibilitas bagi pengguna, tetapi menerapkan batasan tentang cara pengguna dapat berinteraksi dengan UI Anda.
Namun, jika Anda berharap pengguna berinteraksi dengan beberapa elemen secara bersamaan (menggunakan multi-sentuh), Anda harus membatasi sentuhan ke elemen tertentu.
Hal ini lebih fleksibel bagi pengguna, tetapi mempersulit logika untuk memanipulasi UI dan kurang tahan terhadap kesalahan pengguna.
Menambahkan pemroses peristiwa
Di Chrome (versi 55 dan yang lebih baru), Internet Explorer &Edge,
PointerEvents
adalah pendekatan yang direkomendasikan untuk menerapkan gestur kustom.
Di browser lain, TouchEvents
dan MouseEvents
adalah pendekatan yang tepat.
Fitur menarik dari 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
.
Persamaan dengan browser lainnya adalah touchstart
, touchmove
,
touchend
, dan touchcancel
untuk peristiwa sentuh dan jika Anda ingin mengimplementasikan
isyarat yang sama untuk input mouse, Anda harus mengimplementasikan mousedown
,
mousemove
, dan mouseup
.
Jika ada pertanyaan tentang peristiwa yang sebaiknya 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 tersebut. (true
berarti Anda ingin peristiwa tersebut ditampilkan sebelum elemen lainnya.)
Berikut adalah contoh dari 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 pendek di atas, kita hanya menambahkan pemroses peristiwa awal untuk peristiwa mouse. Alasan untuk hal ini adalah karena peristiwa mouse hanya akan dipicu saat kursor diarahkan ke atas elemen yang ditambahkan pemroses peristiwa.
TouchEvents
akan melacak gestur setelah dimulai, terlepas dari tempat
sentuhan terjadi, dan PointerEvents
akan melacak peristiwa, terlepas dari tempat sentuhan
terjadi 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, yang berarti pemroses dapat melacak kursor hingga gestur selesai.
Langkah-langkah yang diambil untuk menerapkannya adalah:
- Tambahkan 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 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 perpindahan.
- Pada peristiwa akhir, hapus gerakan mouse dan listener akhir dari dokumen dan akhiri gestur.
Berikut adalah cuplikan metode handleGestureStart()
kami yang menambahkan peristiwa gerakan
dan 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 gerakan
dan akhir dari dokumen dan melepaskan tangkapan 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 gerakan ke dokumen ini, jika pengguna mulai berinteraksi dengan elemen dan memindahkan gesturnya ke luar elemen, kita akan terus mendapatkan gerakan mouse, terlepas dari lokasinya di halaman, karena peristiwa diterima dari dokumen.
Diagram ini menunjukkan apa yang dilakukan peristiwa sentuh saat kita menambahkan peristiwa gerakan dan akhir ke dokumen setelah gestur dimulai.
Merespons sentuhan dengan efisien
Setelah kita membereskan peristiwa awal dan akhir, kita dapat benar-benar merespons peristiwa sentuh.
Untuk setiap peristiwa awal dan gerak, Anda dapat dengan mudah mengekstrak x
dan y
dari sebuah peristiwa.
Contoh berikut memeriksa apakah peristiwa berasal dari TouchEvent
dengan memeriksa apakah targetTouches
ada. Jika ya, kode ini akan mengekstrak
clientX
dan clientY
dari sentuhan pertama.
Jika peristiwanya adalah PointerEvent
atau MouseEvent
, peristiwa tersebut 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 ada di elemen DOM tempat peristiwa terikat.changedTouches
: daftar sentuhan yang berubah sehingga menyebabkan peristiwa diaktifkan.
Umumnya, targetTouches
memberikan semua yang Anda butuhkan dan inginkan. (Untuk
mengetahui informasi selengkapnya tentang daftar ini, lihat Daftar sentuhan).
Menggunakan requestAnimationFrame
Karena callback peristiwa diaktifkan pada thread utama, kita ingin menjalankan sesedikit mungkin kode dalam callback untuk peristiwa kita, menjaga kecepatan bingkai tetap tinggi dan mencegah jank.
Dengan menggunakan requestAnimationFrame()
, kita memiliki kesempatan untuk mengupdate UI tepat sebelum browser bermaksud menggambar frame dan akan membantu kita memindahkan beberapa tugas dari callback peristiwa.
Jika belum memahami requestAnimationFrame()
, Anda
dapat mempelajari lebih lanjut di sini.
Implementasi umum adalah dengan menyimpan koordinat x
dan y
dari
peristiwa mulai dan gerak serta meminta frame animasi di dalam callback
peristiwa gerak.
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 diperlukan, 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
memberitahu browser untuk memanggilnya sesaat sebelum memperbarui halaman
(yaitu, menggambar setiap perubahan pada halaman).
Dalam callback handleGestureMove()
, pada awalnya kami akan memeriksa apakah rafPending
bernilai salah (false), yang menunjukkan apakah onAnimFrame()
telah dipanggil oleh requestAnimationFrame()
sejak peristiwa pemindahan terakhir. Ini berarti kita hanya memiliki satu requestAnimationFrame()
yang menunggu dijalankan pada satu waktu.
Saat callback onAnimFrame()
dijalankan, kita menetapkan transformasi pada elemen apa pun yang ingin dipindahkan sebelum memperbarui rafPending
ke false
, sehingga peristiwa sentuh berikutnya dapat 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 sentuhan
default dari elemen. Dalam contoh, kita menggunakan touch-action: none
untuk mencegah browser melakukan tindakan 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
agak dianggap sebagai opsi nuklir karena mencegah semua
perilaku browser default. Dalam banyak kasus, salah satu opsi
di bawah ini adalah solusi yang lebih baik.
touch-action
memungkinkan Anda menonaktifkan gestur yang diterapkan oleh browser.
Misalnya, IE10+ mendukung gestur ketuk dua kali untuk zoom. Dengan menetapkan
touch-action
dari manipulation
, Anda mencegah perilaku ketuk dua kali
default.
Hal ini memungkinkan Anda mengimplementasikan gestur ketuk dua kali sendiri.
Di bawah ini adalah daftar nilai touch-action
yang umum digunakan:
Mendukung IE versi lama
Jika Anda ingin mendukung IE10, Anda harus menangani versi awalan vendor dari
PointerEvents
.
Untuk memeriksa dukungan PointerEvents
, biasanya Anda akan mencari
window.PointerEvent
, tetapi di 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 informasi selengkapnya, lihat artikel update dari Microsoft ini.
Referensi
Kelas 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 dalam aplikasi Anda:
Daftar sentuh
Setiap peristiwa sentuh berisi 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 body
dokumen atau ke setiap elemen.
Anda harus melakukannya di belakang pengujian agen-pengguna sehingga hanya berjalan di perangkat iOS.
Menambahkan touch start ke body memiliki keuntungan karena diterapkan ke semua elemen dalam DOM, namun ini mungkin menyebabkan masalah kinerja 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 awal sentuh 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);
}
}
};