holodepth

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
  • usePicker composable · custom emit köprüsü
  • stopPropagation diplomasi · 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:

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.

Hover nerede yaşar?
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.

Aday kümesi · daraltın
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ı

  1. İki şerit karışımı: overlay click canvas pick'i de tetikler.
  2. Derin reaktif mesh: hover move'da proxy thrash.
  3. Allowlist yok: süs mesh yanlış isabet.
  4. Stale rect: resize sonrası kaymış pick.
  5. Overlay yutması: pointer-events savaşının kaybı.
  6. useLoop raycast: gereksiz sürekli test.
  7. Missed yok: seçim temizlenmez.
  8. Mobil hover: coarse cihazda move seli.
  9. R3F onClick ezberi: mesh prop aramak.
  10. 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

  1. Overlay ve canvas ayrı şeritte mi?
  2. usePicker tek Raycaster sahibi mi?
  3. hoveredId shallowRef mi?
  4. pickRoots / layers dar mı?
  5. pick-missed emit var mı?
  6. pointer-events HUD'da yazılı mı?
  7. Mobilde move throttle var mı?
  8. useLoop içi sürekli raycast var mı?
  9. Dinleyici onBeforeUnmount'ta sökülüyor mu?
  10. 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.