Kısa cevap: Pointer events, fare, kalem ve dokunmatik girişi tek bir olay modelinde toplayan DOM olaylarıdır. mousedown, mousemove, mouseup yerine pointerdown, pointermove, pointerup dinlediğinizde aynı kod parmakla, kalemle ve fareyle çalışır. Olay nesnesi PointerEvent türündedir, MouseEvent özelliklerinin hepsini devralır ve buna ek olarak cihaz türünü veren pointerType, her temas noktası için ayrı pointerId, birincil işaretçiyi belirten isPrimary ve basınç, eğim gibi kalem verilerini taşır. Sürükleme gibi etkileşimlerde setPointerCapture() ile olayları bir öğeye sabitler, dokunmatik ekranda tarayıcının kendi kaydırma ve yakınlaştırma davranışını CSS touch-action özelliğiyle denetlersiniz.

Bu yazıda olay türlerini, fare olaylarıyla ilişkiyi, düğme durumlarını, işaretçi yakalamayı, pointercancel olayının neden geldiğini ve çok noktalı dokunuşu MDN'nin Pointer Events belgelerine dayanarak anlatıyoruz. Örnekler tarayıcıya özgüdür ve çalıştırılmadı; API adları MDN sayfalarındaki gibidir. Dinleyici seçenekleri için addEventListener yazısına, serinin tamamı için Web API öğrenme rehberi sayfasına bakın.

Neden pointer events?

MDN'nin çıkış noktası şudur: Web içeriğinin büyük kısmı kullanıcının bir fare kullandığını varsayar, oysa pek çok cihaz kalem ve dokunmatik yüzey gibi başka işaretleme girdileri de sunar. Pointer events bu ihtiyaca yanıt olarak, donanımdan bağımsız bir "işaretçi" kavramı üzerine kurulmuş tek bir olay modeli tanımlar. İşaretçi, ekranda belirli bir koordinatı hedefleyebilen her türlü giriş aygıtıdır: fare imleci, kalem ucu ya da bir parmak teması. Tek model kullanmak kodu sadeleştirir, ama cihaza özel davranış gerektiğinde pointerType özelliği hangi aygıtın olayı ürettiğini söyler.

Olay adları bilinçli olarak fare olaylarına benzetilmiştir ve PointerEvent, MouseEvent arayüzünü genişletir. Yani clientX, clientY, pageX, target, button ve buttons gibi alışık olduğunuz özellikler aynen bulunur. Bu, fare olaylarıyla yazılmış bir kodu pointer olaylarına taşımayı çoğu zaman olay adlarını değiştirmek kadar kolaylaştırır.

Olay türleri

Olay türleri: pointerover, pointerenter, pointerdown, pointermove
OlayNe zaman gelirFare karşılığı
pointeroverİşaretçi bir öğenin isabet sınırına girdiğindemouseover
pointerenterİşaretçi öğeye ya da alt öğelerinden birine girdiğindemouseenter
pointerdownİşaretçi etkin düğme durumuna geçtiğindemousedown
pointermoveİşaretçinin koordinatları değiştiğindemousemove
pointerupİşaretçi etkin düğme durumundan çıktığındamouseup
pointercancelTarayıcı işaretçinin artık olay üretmeyeceğine karar verdiğindeyok
pointeroutİşaretçi sınırdan çıktığında ve birkaç başka durumdamouseout
pointerleaveİşaretçi öğenin isabet sınırından çıktığındamouseleave
gotpointercaptureÖğe işaretçi yakalamayı aldığındayok
lostpointercaptureİşaretçi yakalama bırakıldıktan sonrayok

Listede click, auxclick ve contextmenu olayları da PointerEvent kullanır; MDN bunların etkinleştirme ya da bağlam menüsü isteme gibi daha üst düzey eylemleri temsil ettiğini ve yalnızca işaretçiyle sınırlı olmadığını vurgular. Klavye de click ya da contextmenu tetikleyebilir. Bu yüzden bir düğmenin işini yapmak için pointerdown değil click dinlemek, klavye kullanıcılarını da kapsar. Klavyeyle tetiklenen bir click olayında pointerId değeri -1 olur. pointerrawupdate ise pointerdown ya da pointerup üretmeyen özellik değişikliklerinde gelen ek bir olaydır; desteği için MDN uyumluluk tablosuna bakın.

pointerover ile pointerenter arasındaki fark fare dünyasındakiyle aynıdır. MDN, pointerover ve pointerout olaylarının hedefin alt öğelerinin sınırlarında da tetiklendiğini, pointerenter ve pointerleave davranışının ise alt öğelere girip çıkmaktan etkilenmediği için genellikle daha mantıklı olduğunu belirtir. Bir kartın üzerine gelindiğinde bir kez iş yapmak istiyorsanız pointerenter tercih edin.

pointerdown ile mousedown arasında da ince bir fark var. Fiziksel farede mousedown her düğmeye basışta gelir; pointerdown ise yalnızca hiç düğme basılı değilken ilk düğmeye basıldığında gelir, ardından basılan ek düğmeler yeni bir pointerdown üretmez. Dokunmatikte pointerdown parmak yüzeye değdiğinde, kalemde uç sayısallaştırıcıya temas ettiğinde tetiklenir.

PointerEvent özellikleri

ÖzellikDeğer
pointerTypemouse, pen veya touch; algılanamazsa boş dize
pointerIdOlayı üreten işaretçiye ait benzersiz sayı
isPrimaryBu tür için birincil işaretçi ise true
pressure0 ile 1 arası normalleştirilmiş basınç
width, heightTemas geometrisinin CSS pikseli cinsinden boyutu
tiltX, tiltYKalemin eğimi, -90 ile 90 derece arası
twistKalemin kendi ekseni etrafında dönüşü, 0 ile 359 derece
tangentialPressureKalem gövdesindeki teğetsel basınç, -1 ile 1 arası

pointerType için MDN üç değer tanımlar: "mouse", "pen" ve "touch". Tarayıcı aygıt türünü algılayamazsa değer boş dize olabilir; bunların dışında bir tür desteklenirse değerin üretici önekiyle gelmesi beklenir. Basınç desteklemeyen donanımlarda, örneğin farede, pressure değeri düğme basılıyken 0.5, aksi halde 0 olur. Bu ayrıntı, basınca göre çizgi kalınlığı ayarlayan bir çizim uygulamasında fareyle de makul bir sonuç almanızı sağlar.

javascript
const tuval = document.getElementById("tuval");

tuval.addEventListener("pointerdown", (event) => {
  switch (event.pointerType) {
    case "mouse":
      fareIleBasla(event);
      break;
    case "pen":
      kalemleBasla(event, event.pressure, event.tiltX, event.tiltY);
      break;
    case "touch":
      dokunmaIleBasla(event, event.width * event.height);
      break;
    default:
      console.log(`pointerType ${event.pointerType} desteklenmiyor`);
  }
});

pointerId her etkin işaretçi için diğerlerinden farklıdır; ancak MDN değerin rastgele üretilebileceğini, aygıt hakkında bilgi taşıdığına güvenilmemesi gerektiğini ve yalnızca sayfa ya da oturum ömrü boyunca kararlı olduğunun garanti edildiğini söyler. Ayrıca tarayıcılar arasında tutarsız uygulandığını not eder ve aynı anda birden çok aygıtı güvenilir şekilde ayırt etmek için persistentDeviceId özelliğini işaret eder; bu özelliğin desteği için MDN uyumluluk tablosuna bakın.

Birincil işaretçi ve düğme durumları

Birincil işaretçi ve düğme durumları: Son olaydan beri düğme veya temas değişmedi, Düğmesiz fare hareketi, ucu değmeden gezinen kalem, Sol fare düğmesi, dokunma teması, kalem...

Bir cihazda aynı anda birden fazla işaretçi olabilir: dokunmatik ekranı olan bir dizüstü bilgisayarda fare ve parmak, ya da ekranda aynı anda üç parmak. isPrimary her işaretçi türü için bir ana işaretçiyi işaretler. Fare her zaman birincildir. Dokunmatikte ekranda başka etkin dokunuş yokken başlayan temas birincil sayılır; kalemde de benzer kural geçerlidir. Farklı türler aynı anda kullanılırsa her türün kendi birincil işaretçisi olur. Yalnızca tek noktalı etkileşim desteklemek istiyorsanız birincil olmayan olayları yok saymanız yeterlidir. MDN yalnızca birincil işaretçilerin uyumluluk fare olayları ürettiğini de belirtir.

Düğme durumu için pointer events, MouseEvent üzerinden gelen button ve buttons özelliklerini kullanır. button hangi düğmenin durumunun değiştiğini, buttons o anda hangi düğmelerin basılı olduğunu bit maskesi olarak verir. MDN'nin tablosundan seçilmiş değerler:

Durumbuttonbuttons
Son olaydan beri düğme veya temas değişmedi-1belirtilmemiş
Düğmesiz fare hareketi, ucu değmeden gezinen kalembelirtilmemiş0
Sol fare düğmesi, dokunma teması, kalem teması01
Orta fare düğmesi14
Sağ fare düğmesi, kalem gövde düğmesi22
Kalem silgi düğmesi532

Pratikte bu tablo, pointermove sırasında "sürükleme mi yoksa sadece üzerinden geçme mi" sorusunu yanıtlamanızı sağlar: event.buttons === 0 ise hiçbir düğme ya da temas yoktur, işaretçi sadece hareket ediyordur.

İşaretçi yakalama: setPointerCapture

Bir kaydırıcıyı ya da sürüklenebilir bir kartı düşünün. Kullanıcı öğeye basıp hızla hareket ettiğinde işaretçi öğenin sınırından çıkar ve normal isabet testine göre sonraki pointermove olayları başka öğelere gider. İşaretçi yakalama bu sorunu çözer: element.setPointerCapture(pointerId) çağrısından sonra o işaretçinin olayları, işaretçi nerede olursa olsun yakalayan öğeye yönlendirilir. Yakalama releasePointerCapture() ile ya da pointerup veya pointercancel geldiğinde kendiliğinden biter. hasPointerCapture() yakalamanın sürüp sürmediğini sorar.

Yakalama sürerken pointerover, pointerenter, pointerleave ve pointerout olayları gelmez. Doğrudan dokunmayla çalışan dokunmatik tarayıcılarda pointerdown sırasında örtük bir yakalama da başlatılır. setPointerCapture() ve releasePointerCapture(), verilen pointerId etkin bir işaretçiyle eşleşmezse NotFoundError türünde bir DOMException fırlatır; bu hata adı için DOMException ve güvenli bağlam hataları yazısına bakabilirsiniz. Ayrıca MDN, öğeyi DOM içinde taşıyacaksanız setPointerCapture() çağrısını taşıma işleminden sonra yapmanızı, aksi halde yakalamanın izini kaybedebileceğini not eder.

html
<div id="kaydirici">SÜRÜKLE</div>
javascript
const kaydirici = document.getElementById("kaydirici");

function kaymayaBasla(e) {
  kaydirici.onpointermove = kaydir;
  kaydirici.setPointerCapture(e.pointerId);
}

function kaymayiBitir(e) {
  kaydirici.onpointermove = null;
  kaydirici.releasePointerCapture(e.pointerId);
}

function kaydir(e) {
  kaydirici.style.transform = `translate(${e.clientX - 70}px)`;
}

kaydirici.onpointerdown = kaymayaBasla;
kaydirici.onpointerup = kaymayiBitir;

Bu örnek MDN'nin setPointerCapture() sayfasındaki kaydırıcı örneğinin Türkçeleştirilmiş hâlidir. MDN örneğinde öğeye CSS ile touch-action: none da verilir; aksi halde dokunmatik ekranda yatay sürükleme tarayıcı tarafından kaydırma olarak yorumlanabilir. Yakalama başladığında gotpointercapture, bittiğinde lostpointercapture olayları gelir; durum göstergesi ya da temizlik kodunu bu olaylara bağlamak, yakalamanın hangi yolla bittiğinden bağımsız çalışmanızı sağlar.

pointercancel ve touch-action

pointercancel, tarayıcının o işaretçiden artık olay gelmeyeceğine karar verdiği durumlarda tetiklenir. MDN'nin listelediği nedenler arasında kullanıcının uygulama değiştirmesi gibi donanım olayları, işaretçi etkinken ekran yönünün değişmesi, avuç içi reddi gibi kazara girdinin tespit edilmesi, touch-action CSS özelliğinin girdinin sürmesini engellemesi ve kullanıcının çok sayıda işaretçiyle aynı anda etkileşmesi var. En sık karşılaşılan durum ise şudur: pointerdown geldikten sonra tarayıcı hareketi kaydırma, yakınlaştırma ya da sayfa kaydırma olarak yorumlar ve işaretçiyi iptal eder. pointercancel sonrasında tarayıcı ayrıca pointerout ve ardından pointerleave gönderir.

Bu nedenle çizim, sürükleme ya da kendi hareket algılamanızı yaptığınız alanlarda tarayıcının yerel dokunma davranışını CSS touch-action ile sınırlamanız gerekir. MDN'nin özetine göre auto tarayıcıya serbestlik verir, none yerel dokunma davranışını kapatır, pan-x ve pan-y yalnızca yatay ya da dikey kaydırmaya izin verir, manipulation ise kaydırma ve yakınlaştırmayla sınırlar. CSS ayrıntıları bu serinin konusu değildir; burada bilmeniz gereken, pointercancel alıyorsanız ilk bakacağınız yerin bu özellik olduğudur. Her durumda pointercancel dinleyip başlamış bir etkileşimi temiz şekilde geri almak iyi bir alışkanlıktır.

Fare olaylarıyla uyumluluk

Web içeriğinin çoğu yalnızca fare olaylarıyla yazıldığı için tarayıcılar pointer events desteklese bile fare olaylarını da üretir. MDN buna uyumluluk fare olayları der: Tarayıcı genel işaretçi girdisini fare olaylarına eşleyebilir. Bu, aynı dokunuş için hem pointerdown hem mousedown dinliyorsanız işinizin iki kez çalışabileceği anlamına gelir. Belirli uyumluluk fare olaylarını pointerdown olayını iptal ederek engelleyebilirsiniz, ancak üç sınır vardır: Fare olayları yalnızca işaretçi basılıyken engellenebilir, düğmesi basılı olmayan fare gibi üzerinde gezinen işaretçilerin fare olayları engellenemez ve mouseover, mouseout, mouseenter, mouseleave hiçbir zaman engellenmez. En temiz çözüm, aynı iş için ya yalnızca pointer olaylarını ya yalnızca fare olaylarını dinlemektir.

Uygulamalı mini örnek: çok noktalı çizim

MDN'nin "Using Pointer Events" rehberi, canvas üzerinde çok parmakla çizim yapan bir uygulama kurar. Temel fikir, her temasın son konumunu pointerId anahtarlı bir Map içinde tutmaktır. Aşağıdaki kod o rehberin sadeleştirilmiş hâlidir; çizim çağrıları Canvas API'den gelir ve Canvas çizim rehberinde anlatılır.

html
<canvas id="tuval" width="600" height="400">Tarayıcınız canvas desteklemiyor.</canvas>
javascript
const tuval = document.getElementById("tuval");
const ctx = tuval.getContext("2d");
const temaslar = new Map(); // pointerId ile son konum

tuval.addEventListener("pointerdown", (event) => {
  temaslar.set(event.pointerId, { x: event.offsetX, y: event.offsetY });
});

tuval.addEventListener("pointermove", (event) => {
  const onceki = temaslar.get(event.pointerId);
  if (!onceki) {
    return; // bu işaretçi tuvalde başlamadı
  }
  ctx.beginPath();
  ctx.moveTo(onceki.x, onceki.y);
  ctx.lineTo(event.offsetX, event.offsetY);
  ctx.lineWidth = event.pointerType === "pen" ? 1 + event.pressure * 6 : 4;
  ctx.stroke();
  temaslar.set(event.pointerId, { x: event.offsetX, y: event.offsetY });
});

function bitir(event) {
  temaslar.delete(event.pointerId);
}
tuval.addEventListener("pointerup", bitir);
tuval.addEventListener("pointercancel", bitir);

MDN rehberi tuvale CSS ile touch-action: none verir, böylece parmakla çizim sayfayı kaydırmaz. Çok hızlı hareketlerde tarayıcı birden fazla konum güncellemesini tek bir pointermove olayında birleştirir; bu performansı artırır ama ayrıntı kaybettirir. event.getCoalescedEvents() bu birleştirilmiş olayların tamamını bir dizi olarak verir ve MDN çizim uygulamalarında daha yumuşak eğriler için kullanılabileceğini söyler. getPredictedEvents() ise tarayıcının sonraki konumlar için yaptığı tahminleri döndürür. Ekranın aynı anda kaç dokunma noktası desteklediğini navigator.maxTouchPoints ile öğrenebilirsiniz.

En iyi uygulamalar ve sık yapılan hatalar

En iyi uygulamalar ve sık yapılan hatalar: pointercancel dinlememek, touch-action vermemek, Aynı iş için hem mouse hem pointer olayı dinlemek, Etkinleştirme için pointerdown...

MDN'nin pointer events için önerdiği en iyi uygulamalar kısa ve nettir: Olay işleyicilerinde mümkün olduğunca az iş yapın, işleyicileri belgenin tamamı yerine belirli bir hedef öğeye ekleyin ve hedef öğeyi en büyük temas yüzeyi olan parmak dokunuşunu karşılayacak kadar büyük tutun; küçük hedefler yanındaki öğelerin olaylarını tetikleyebilir. Bunlara sahada sık görülen hataları da ekleyelim:

  • pointercancel dinlememek: Tarayıcı hareketi kaydırmaya çevirdiğinde etkileşim yarıda kalır, sürüklenen öğe havada asılı kalır.
  • touch-action vermemek: Dokunmatikte sürükleme ya da çizim başlar başlamaz iptal edilir.
  • Aynı iş için hem mouse hem pointer olayı dinlemek: Uyumluluk fare olayları yüzünden kod iki kez çalışabilir.
  • Etkinleştirme için pointerdown kullanmak: Klavye kullanıcıları dışarıda kalır; düğme işleri için click dinleyin.
  • pointerover ile kart üzerine gelmeyi izlemek: Alt öğelerde tekrar tekrar tetiklenir; pointerenter daha uygundur.
  • pointerId değerini kalıcı kimlik sanmak: Değer yalnızca oturum boyunca kararlıdır ve aygıt hakkında bilgi taşımaz.
  • Yakalamayı DOM taşımasından önce kurmak: MDN, append() gibi bir taşıma varsa yakalamayı sonrasında kurmanızı önerir.

Sık Sorulan Sorular

Touch events yerine pointer events mı kullanmalıyım?

Pointer events, fare, kalem ve dokunmayı tek modelde birleştirdiği için aynı kodu tüm girdilerde çalıştırmanın en doğrudan yoludur. MDN'nin çizim rehberi de touch events örneğinin pointer events ile yeniden yazılmış hâlidir. Dokunmaya özgü ayrıntılara ihtiyaç duyduğunuz özel durumlar için MDN'nin Touch events belgeleri ayrı olarak duruyor.

pointerType ile mobil cihazı mı algılamalıyım?

Hayır. pointerType cihazın türünü değil, o olayı üreten girdinin türünü söyler; dokunmatik ekranlı bir dizüstünde aynı sayfada hem "mouse" hem "touch" gelebilir. Değeri yalnızca girdiye özgü davranış için kullanın.

İşaretçi yakalama ile Pointer Lock aynı şey mi?

Hayır. MDN ikisini ayırır: İşaretçi yakalama olayları belirli bir öğeye yönlendirir; Pointer Lock API ise işaretçinin fiziksel olarak bir bölgeden çıkmasını engeller.

pointermove olayı neden her piksel için gelmiyor?

Tarayıcı performans için ardışık güncellemeleri birleştirir. Tüm ara konumlara ihtiyacınız varsa getCoalescedEvents() kullanın; ağır işi her olayda değil her animasyon karesinde yapmak için requestAnimationFrame yazısına bakın.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Web API öğrenme rehberi · addEventListener kullanımı · Olay yayılımı ve delegasyon · Klavye olayları · Canvas çizim rehberi · requestAnimationFrame