Framework entegrasyonları · Vue + Three
Olay Bağları & Picking: Şablon Şeridi ile Canvas İsabeti
Vue ürününde tıklama çoğu zaman iki paralel yoldan gelir:
DOM overlay üzerinde @click /
@pointer* ile canvas raycast isabeti.
Bu sayfa composable emit köprüsünü, shallowRef hover disiplinini ve
allowlist picking'i anlatır · Raycaster matematiğinin tekrarı değildir.
Holodepth bu yaprağı Vue olay temeli veya SFC sözlüğü olarak yazmaz. R3F isabet prop yüzeyi Pointer olayları & picking yaprağındadır. Angular HostListener köprüsü Pointer bağları & picking yaprağındadır. Raycaster / intersect mantığı Three etkileşim omurgasındadır (Mesh picking , Layer filtering). Mount kökü Mount & kök sözleşmesi ; kare metronom Kare döngüsü & watchEffect yapraklarındadır. HTML Canvas pointer Pointer sistemi ile akrabadır. glTF clickable metadata bu kapının konusu değildir.
Vue + Three yolculuğunun omurgası: mount → harita → tik → kargo → bağ → kaçış. Bu sayfa bağ kapısıdır: kullanıcı girdisi sahneye nasıl bağlanır, seçim üst şablona nasıl emit edilir, hover neden derin reaktif olmamalıdır? Önceki kapı asenkron varlık ömrüydü; sonraki kapı reaktif kaçış ve performanstır.
Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «Overlay @click ile canvas pick ayrı şerittir. usePicker composable emit taşır. Hover shallowRef ile id tutar, Object3D derin reaktif değildir. stopPropagation ürün kararıdır. Allowlist katmanları aday kümesini daraltır. Mobil dokunmada move seli throttle edilir. useLoop içinde sürekli raycast varsayılan değildir. Sonraki kapı reaktif kaçış & performanstır.»
Bu sayfanın sınırı · komşu konularla ayrım
Bu sayfa Vue + Three'de olay bağları / picking köprüsünü anlatır:
- Şablon
@click/@pointer*overlay vs canvas raycast usePickercomposable · custom emit köprüsüstopPropagationdiplomasi · montaj / parça ayrımı- Hover
shallowRef· Object3D derin reaktif değil - Picking allowlist · layers / aday kümesi
- Mobil dokunma · pointer coarse · move throttle
Bilinçli olarak dışarıda bırakılan konular:
- R3F onPointer* prop yüzeyi → R3F · Pointer & picking
- Angular HostListener / Output → Angular · Pointer bağları
- Mount / provide kökü → Mount & kök sözleşmesi
- Reaktif şablon haritası → Reaktif şablon → Object3D
- watchEffect / rAF metronom → Kare döngüsü & watchEffect
- Loader / onBeforeUnmount dispose → Yükleme & onBeforeUnmount
- Ağır reaktif kaçış → Reaktif kaçış & performans
- Raycast BVH / NDC formülü · Vue 101 · glTF şema → format izlekleri
Kısa ayrım: Bu sayfa «Vue şablonu ile canvas isabeti nasıl bağlanır?» · R3F picking «Fiber prop yüzeyi» · Angular picking «HostListener + Output» · Three raycast «ışın geometrisi».
İki şerit: overlay şablon vs canvas isabet
Vue ürünleri sıkça aynı ekranda hem HTML toolbar hem WebGL sahnesi taşır.
Toolbar düğmesi gerçek bir DOM hedefidir: @click doğrudan
çalışır, Raycaster devreye girmez. Sahne seçimi ise canvas üzerindeki
pikselden ışın isabeti ile bulunur; DOM «hangi mesh?» bilmez. Holodepth
dili: iki şerit, tek ürün politikası. Karıştırırsanız ya HUD
tıklanmaz ya 3D pick ölür.
R3F'de isabet mesh prop'una yazılır; Angular'da HostListener canvas'tan
girer. Vue'da tipik desen: Stage SFC'sinde canvas host'a
@pointerdown / @click bağlamak veya composable
içinde addEventListener kurmak; overlay panelde ise normal
Vue olayları. İkisini aynı handler'a yığmak teşhisi zorlaştırır.
DOM şeridi HTML'de kalır; isabet şeridi composable'da yaşar.
usePicker composable ve emit köprüsü
Holodepth ürün varsayılanı: picking mantığını Stage içine gömmek yerine
usePicker composable'ında toplamak. Composable tek
Raycaster sahibi, NDC haritası, allowlist aday kümesi ve
«missed» temizliğini mühürler. Üst bileşene sonuç
emit('part-picked', id) veya
emit('pick-missed') ile taşınır. Angular
@Output partSelected ile aynı ürün ihtiyacı, Vue yüzünde
custom event veya v-model köprüsüdür.
Composable, inject edilen kökten (useThreeRoot) kamera ve
sahneye erişir; her child'da yeni Raycaster yaratmayın. Dinleyicileri
onMounted / onBeforeUnmount hattına yazın;
dispose derinliği
Yükleme & onBeforeUnmount
yaprağındadır, burada yalnız «dinleyici ömür = Stage ömrü» mührü.
TresJS tarzı kütüphaneler mesh düzeyinde olay soyutlayabilir; altta yine host koordinatı + raycast vardır. Composable sözleşmesi kütüphane seçiminden bağımsız kalır: tek giriş, dar aday, seyrek emit.
Hover: shallowRef, derin reaktif Object3D değil
Hover durumunu ref(mesh) veya mesh'i
reactive() içine almak proxy tuzağına düşürür: her materyal
okuması bağımlılık adayı olur, pointermove selinde graf şişer. Holodepth:
hover hedefini shallowRef<string | null>(null) ile
id olarak tutun; görsel güncellemeyi Object3D tarafında doğrudan
yapın veya seyrek watch ile senkronlayın.
R3F'de useState(hovered) benzeri maliyet burada Vue
reaktivitesine taşınır. Sürekli over-out ile şablon re-render yağmuru
üretmeyin; cursor stilini host'ta sınırlı tutun, her move'da
selectedMaterial computed'u tetiklemeyin.
| Katman | Vue yüzü | Holodepth kararı |
|---|---|---|
| Seçim id | shallowRef / ref string |
Şablona seyrek yansır |
| Mesh referansı | markRaw / inject kök |
Derin reaktif değil |
| Emissive / renk | Object3D doğrudan | Move'da graf yok |
| Cursor | host CSS / body sınırlı | Unmount'ta geri al |
Hover id taşır; mesh proxy'lenmez.
Şablon @click ve @pointer* host bağları
Canvas host div veya <canvas> üzerinde
@pointerdown.prevent, @click,
@pointermove bağlamak Vue'nun doğal yüzüdür. Handler ince
kalmalı: koordinatı composable'a ilet, raycast orada. Şablonda ağır
iş veya watchEffect açmayın; bu, kare yaprağındaki graf
vergisinin etkileşim kuzenidir.
Overlay toolbar'da @click.stop bilinçli kullanılır: olay
Stage host'una bubble etmesin. Tersi durumda «toolbar tıkladım, sahne
de seçildi» çift eylemi görülür. Modifier sırası ürün spesifikasyonunda
yazılmalıdır: overlay mi önce, canvas mı?
.passive ve { passive: true } addEventListener
varyantı scroll/jank için move dinleyicilerinde değerlidir; composable
içinde tercih edin. Vue template modifier'ları ile native seçenekleri
karıştırmayın; tek giriş kapısında birleştirin.
stopPropagation diplomasi
Montaj grubu ve parça mesh'i aynı pick ağacında yaşarsa, parça seçimi
üst gruba da «montaj seçildi» sinyali taşıyabilir. R3F bubbling prop
hiyerarşisine benzer; Vue'da DOM bubble ile Three parent-child ayrı
düzlemlerdir. Holodepth: parça eylemi montaja gitmesin istiyorsanız
composable içinde «ilk isabet object» ile politika yazın; overlay'de
@click.stop kullanın.
Missed (boş tıklama): emit('pick-missed') veya
selectedId = null ile seçimi temizleyin. R3F
onPointerMissed ile aynı ürün ihtiyacı. Temizlik olmadan
«bir kez seçtim bir daha bırakamıyorum» ticket'ı gelir.
stopPropagation'ı varsayılan sihir sanmayın. Ticket'ta tek cümle: «Parça id emit eder; montaj ayrı butondan» veya «Her isabet montaja bubble eder». Diplomasi belgelenmezse her yeni mesh sürpriz üretir.
Allowlist: layers ve aday kümesi
Tüm sahneyi intersectObjects(scene.children, true) ile
taramak hem pahalıdır hem süs mesh'lerin yanlış isabet almasına yol açar.
Holodepth: seçilebilir köklerin allowlist'i composable'da mühürlenir;
pickRoots dizisi veya userData.pickable filtresi.
Three layers maskesi daraltma için
Layer filtering
yaprağına bırakılır; burada mühür: aday kümesi ürün listesidir,
scene dump değildir.
Görünmez collider allowlist'te bilinçli durmalıdır; dekoratif cam raycast kapalı tercih edilir. Öndeki şeffaf yüzey arkadaki butonu «çalar»; visible=false her sürümde raycast'ı kapatmayabilir.
| Küme | Ne zaman? | Risk |
|---|---|---|
| pickRoots allowlist | Ürün vitrini | Liste güncel mi? |
| layers mask | HUD / debug ayrımı | Kamera layer uyumu |
| userData.pickable | Dinamik montaj | Yanlış flag mirası |
| Tüm scene | Yalnız debug | Prod'da pahalı |
Mobil dokunma ve pointer coarse
Dokunmatik cihazlarda pointermove seli masaüstünden daha
sık ve daha geniş hedef alanı ister. Holodepth: hover önizlemesini mobilde
kapatın veya throttle edin (~50ms); «her parmak pikseli pick» varsayılan
olmasın. matchMedia('(pointer: coarse)') ile profil
ayırımı ürün kararıdır: vitrin mobilde yalnız tap, hover yok.
touch-action CSS'i canvas host'ta scroll ile çakışmayı
önler; OrbitControls ile birlikte düşünün. 300ms gecikme tuzağı modern
tarayıcılarda azaldı; yine de @click yerine
@pointerup tercihini bilinçli seçin. Çift tetik (touch +
synthetic click) için composable'da tek giriş kapısı koruyun.
Mobil DPR yüksekken pick rect'i stale kalırsa isabet kayar; mount yaprağındaki ResizeObserver sözleşmesi buranın zeminidir. Rect güncel değilse NDC yanlış, mesh «tıklanmıyor» sanılır.
DOM overlay: pointer-events diplomasi
Stage şablonunda canvas üstü HUD sık görülür. Toolbar kapsayıcısında
pointer-events: none, tıklanabilir düğmede
pointer-events: auto bölgeleri net yazılmazsa ya 3D pick
ölür ya toolbar tıklanmaz. Holodepth politika cümlesi:
hangi dikdörtgen DOM'a, hangi bölgesi canvas'a?
Tam ekran şeffaf div «modal arka plan» olarak canvas hit'ini yutar.
Vue v-show ile gizlenen overlay DOM'da kalıyorsa aynı risk
devam eder. Görünmez katmanların pointer-events değerini kontrol listesine
alın.
Overlay hit yerse, composable Raycaster'a iş kalmaz.
useLoop içinde sürekli raycast yok
useLoop callback'i içinde her kare
raycaster.intersectObjects çalıştırmak, pointer olay
yolunu bypass eder ve çoğu üründe israftır. Holodepth varsayılanı:
pick pointer olayında; drag/snap gibi istisnalar seyreltilmiş test.
Sürekli raycast
Continuous vs. single raycast
yaprağında derinleşir.
Move selini watchEffect veya her karede
selectedId yazmak reaktif vergi üretir. Seçim id değişince
güncelleyin; her pikselde değil. onDemand invalidate ihtiyacı kare
yaprağındadır; burada: etkileşim sonrası görsel güncelleme zincirini
koparmayın.
Güvenli bağ: simüle pick composable
Örnekler öğretim amaçlıdır; sürümü doğrulayın. Vue olay temeli anlatılmaz. Pick id ile yetki açmayın; sunucu doğrulaması gereken eylemlerde mesh tıklaması yalnızca sinyal kabul edilir.
Simüle · usePicker composable + allowlist.
import { onBeforeUnmount, onMounted, shallowRef, type Ref } from 'vue'
import * as THREE from 'three'
export type PickEmit = {
(e: 'part-picked', id: string): void
(e: 'pick-missed'): void
}
export function usePicker(options: {
hostRef: Ref<HTMLElement | null>
camera: Ref<THREE.Camera | null>
/** Allowlist · tüm scene değil */
pickRoots: Ref<THREE.Object3D[]>
emit: PickEmit
}) {
const hoveredId = shallowRef<string | null>(null)
const raycaster = new THREE.Raycaster()
const pointer = new THREE.Vector2()
function pickAt(clientX: number, clientY: number): string | null {
const host = options.hostRef.value
const camera = options.camera.value
if (!host || !camera) return null
const rect = host.getBoundingClientRect()
if (rect.width < 1 || rect.height < 1) return null
pointer.x = ((clientX - rect.left) / rect.width) * 2 - 1
pointer.y = -((clientY - rect.top) / rect.height) * 2 + 1
raycaster.setFromCamera(pointer, camera)
const roots = options.pickRoots.value
if (!roots.length) return null
const hits = raycaster.intersectObjects(roots, true)
if (!hits.length) return null
return hits[0].object.name || hits[0].object.uuid
}
let moveLast = 0
function onPointerDown(ev: PointerEvent) {
const id = pickAt(ev.clientX, ev.clientY)
if (id) options.emit('part-picked', id)
else options.emit('pick-missed')
}
function onPointerMove(ev: PointerEvent) {
const now = performance.now()
if (now - moveLast < 50) return
moveLast = now
hoveredId.value = pickAt(ev.clientX, ev.clientY)
}
onMounted(() => {
const host = options.hostRef.value
if (!host) return
host.addEventListener('pointerdown', onPointerDown)
host.addEventListener('pointermove', onPointerMove, { passive: true })
})
onBeforeUnmount(() => {
const host = options.hostRef.value
host?.removeEventListener('pointerdown', onPointerDown)
host?.removeEventListener('pointermove', onPointerMove)
hoveredId.value = null
})
return { hoveredId }
}
Simüle · StagePick.vue: şablon bağları + emit. Parça stopPropagation overlay'de; canvas pick composable'da.
<script setup>
import { ref, shallowRef } from 'vue'
import { usePicker } from './usePicker'
import { useThreeRoot } from './useThreeRoot'
import { pickableRoots } from './pickableRoots'
const emit = defineEmits<{
'part-picked': [id: string]
'pick-missed': []
}>()
const hostRef = ref(null)
const selectedId = ref<string | null>(null)
const { camera } = useThreeRoot()
const roots = shallowRef(pickableRoots())
const { hoveredId } = usePicker({
hostRef,
camera,
pickRoots: roots,
emit,
})
function onToolbarClick() {
// Overlay şeridi · canvas pick'e karışmaz
selectedId.value = null
emit('pick-missed')
}
</script>
<template>
<div class="stage-pick" style="position:relative;width:100%;height:100%;min-height:360px">
<div
ref="hostRef"
class="stage-pick__canvas-host"
style="width:100%;height:100%"
role="img"
aria-label="3D seçim sahnesi"
/>
<div
class="stage-pick__hud"
style="position:absolute;top:12px;left:12px;pointer-events:none"
>
<button
type="button"
style="pointer-events:auto"
@click.stop="onToolbarClick"
>
Seçimi temizle
</button>
<p v-if="hoveredId" style="pointer-events:none;margin-top:8px">
Hover: {{ hoveredId }}
</p>
<p v-if="selectedId" style="pointer-events:none">
Seçili: {{ selectedId }}
</p>
</div>
</div>
</template>
Güvenlik: cursor'u document.body'de unutmayın; unmount'ta
geri alın. Dinleyicileri composable ömrüne yazın. Kullanıcı string'ini
pick callback'inde eval etmeyin.
Picking tuzakları
- İki şerit karışımı: overlay click canvas pick'i de tetikler.
- Derin reaktif mesh: hover move'da proxy thrash.
- Allowlist yok: süs mesh yanlış isabet.
- Stale rect: resize sonrası kaymış pick.
- Overlay yutması: pointer-events savaşının kaybı.
- useLoop raycast: gereksiz sürekli test.
- Missed yok: seçim temizlenmez.
- Mobil hover: coarse cihazda move seli.
- R3F onClick ezberi: mesh prop aramak.
- Angular HostListener ezberi: Output yerine global bus.
Üç etkileşim profili
-
01 · Bağ
Vitrin tap seçimi
- Giriş
- pointerdown composable
- Aday
- pickRoots allowlist
- Hover
- Kapalı · mobil öncelik
-
02 · Bağ
Masaüstü hover önizleme
- State
- shallowRef hoveredId
- Move
- 50ms throttle
- Risk
- Her move şablon render
-
03 · Bağ
HUD + canvas karışık
- Overlay
- @click.stop · pointer-events
- Sahne
- usePicker emit
- Risk
- Şeffaf div hit yutması
Holodepth perspektifleri ve sonraki kapı
İki şeridi ayırın
Overlay Vue olayı; canvas pick composable. Aynı handler'a yığmayın.
Hover id, mesh değil
shallowRef ile taşıyın; Object3D markRaw hattında kalsın. Derin reaktif pick state'i sessiz jank üretir.
Allowlist ürün sözleşmesidir
pickRoots güncellenmezse yeni parça «tıklanmaz». Layers maskesi bilinçli seçimdir.
Teşhis maddeleri
- Overlay ve canvas ayrı şeritte mi?
- usePicker tek Raycaster sahibi mi?
- hoveredId shallowRef mi?
- pickRoots / layers dar mı?
- pick-missed emit var mı?
- pointer-events HUD'da yazılı mı?
- Mobilde move throttle var mı?
- useLoop içi sürekli raycast var mı?
- Dinleyici onBeforeUnmount'ta sökülüyor mu?
- R3F prop, Angular HostListener veya glTF bu sayfaya sızmış mı?
Holodepth içgörüsü
«Vue'da tıklamıyor» çoğu zaman raycast formülü değil; overlay pointer-events, stale rect, allowlist veya shallowRef ihlalidir. Önce iki şerit diplomasiini okuyun; sonra composable emit köprüsünü mühürleyin.
Sonraki kapı: reaktif kaçış & performans
Etkileşim oturunca kalan soru: ne zaman markRaw / shallowRef yeter, ne zaman daha agresif reaktif kaçış gerekir? Sonraki sayfa Reaktif kaçış & performans bu eşiği anlatır. Vue + Three izleğinin son yaprağıdır. Önceki kapı Yükleme & onBeforeUnmount dispose borusunu mühürledi; bağ kapısı burada, kaçış sonrakindedir.