holodepth

Framework entegrasyonları · Vue + Three

Mount & Kök Sözleşmesi: Sahne Nerede Doğar?

Vue ürününde WebGL sahnesi çoğu zaman bir mount noktasının tuvalinde açılır. Bu sayfa createApp / onMounted / provide ile kök sözleşmeyi anlatır: Vue 101 veya Composition API turu değil · Three ↔ mount kapısı.

Holodepth bu yaprağı «Vue nedir?» dersi veya R3F <Canvas> kopyası olarak yazmaz. R3F kök mührü Canvas & kök sözleşme yaprağındadır; Angular kardeş köprüsü Host & kök sözleşmesi yaprağındadır. Mercek burada mount + ref + provide/inject ömrü. Kare metronom Kare döngüsü & watchEffect yaprağındadır. Dispose ve loader borusu Yükleme & onBeforeUnmount yaprağındadır. glTF şema format izleklerindedir.

Vue + Three yolculuğunun omurgası şu zincirde okunur: mount → harita → tik → kargo → bağ → kaçış. Bu sayfa mount kapısıdır: sahne nerede doğar, renderer kime aittir, tek WebGL bağlamı nasıl mühürlenir? Sonraki yapraklar şablon grafiği, kare döngüsü, yükleme ömrü, olay bağları ve reaktif kaçışı derinleştirir.

Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «Mount bileşeni gümrük masasıdır. Renderer onMounted'ta açılır, onBeforeUnmount'ta kapanır. Template ref CSS kutusu ile backing store'u ayırır. provide/inject kökü alt ağaca taşır. Tek canlı context varsayılandır. SSR'de ClientOnly kapısı zorunludur. Sonraki kapı reaktif şablon haritasıdır.»

Bu sayfanın sınırı · komşu konularla ayrım

Bu sayfa Vue + Three'de mount kök sözleşmesini anlatır:

  • createApp / mount noktası sahipliği
  • onMounted açılış · template ref · boyut / DPR
  • provide / inject ile renderer + sahne kökü
  • Tek context · SSR / ClientOnly kapısı
  • Üç mount profili (ham Three · TresJS tarzı · Nuxt)

Bilinçli olarak dışarıda bırakılan konular:

Kısa ayrım: Bu sayfa «Vue mount kapıyı nasıl mühürler?» · R3F Canvas «Fiber kapıyı nasıl mühürler?» · Angular host «bileşen tuvali kapıyı nasıl mühürler?» · Şablon haritası «kapının arkasında reaktif ağaç».

Gümrük masası: mount noktası

Imperatif demoda çoğu ekip index.html içine <canvas> koyup script'te renderer açar. Vue ürününde tuval bir SFC şablonuna aittir: stil, boyut, yaşam döngüsü ve provide hattı bu masadan geçer. Holodepth: mesh'i yanlış yazmak sahneyi bozar; mount'u yanlış mühürlemek tüm oturumu bozar.

createApp(App).mount('#app') çağrısı DOM ağacını Vue'ya bağlar; Three kökü ise tipik olarak ayrı bir Stage/Scene bileşeninin içinde doğar. TresJS tarzı <TresCanvas> bu masayı soyutlar; ham Three kullanıyorsanız sözleşme aynıdır: kim canvas DOM'unu tutar, kim WebGLRenderer'ı yaratır, kim yok eder?

Mount masası üç soruya cevap verir: (1) WebGL hangi DOM düğümünde? (2) Renderer hangi bileşen ömrüne yazılı? (3) Alt bileşenler köke nasıl erişir? Bu üçlü mühürlenmeden reaktif şablon veya picking yapraklarına geçmek erken adımdır.

Dekoratif şablon taşımaz; mount bileşeni sözleşmeyi taşır.

onMounted: kapı açılışı

setup() veya <script setup> içinde template ref henüz DOM'a bağlı değildir. Holodepth kök kuralı: renderer ve ilk setSize tipik olarak onMounted (veya eşdeğeri «DOM hazır» geri çağrısı) sonrasındadır. Erken erişim «null ref» veya 0×0 tampon üretir.

Kapanış: rota değişince bileşen ölür; GL kaynakları onBeforeUnmount / onUnmounted hattına yazılır. Derin dispose, texture ve loader sahipliği Yükleme & onBeforeUnmount yaprağındadır; burada mühür: açılış ve kapanış aynı mount ömrüne bağlıdır.

nextTick bazen layout settle için gereklidir: özellikle flex/grid içinde ilk clientWidth sıfır dönebilir. Holodepth: onMounted içinde bir kez ölç, ResizeObserver ile sürdür; her reaktif güncellemede renderer yaratma.

Template ref: iki defter bağlantısı

CSS kutusu (layout) ile backing store (piksel tamponu) yine ayrıdır. Mount şablonunda width/height: 100% ve min-height yoksa R3F'deki «siyah kutu» veya Angular'daki «sıfır yükseklik» tuzağını Vue'da da yaşarsınız. Holodepth: ref="canvasHost" ile tuvali alın; clientWidth/clientHeight ve devicePixelRatio tavanıyla setSize / setPixelRatio yazın.

Mount defterleri · karıştırmayın
Defter Vue yüzü Kök kararı
Layout Host CSS / flex / min-height Tuval görünür alan
Backing store setSize + DPR tavanı Fill-rate bütçesi
Yaşam onMounted ↔ onBeforeUnmount Context ömrü
Kök erişim provide / inject Alt SFC'lerin sahne sözleşmesi

Container ref ile canvas ref ayrımı: bazı ekipler renderer'ı doğrudan <div ref="host"> üzerinde appendChild(canvas) ile kurar; bazıları şablonda <canvas ref="gl"> kullanır. İkisi de geçerli; karar ürün stiline bağlıdır. Önemli olan ref'in onMounted'ta dolu olması ve parent'ın ölçülebilir bir kutusu olmasıdır.

provide/inject: kök taşıyıcı

Mount bileşeni renderer ve THREE.Scene (veya TresJS eşdeğeri) yarattıktan sonra bunları provide(THREE_ROOT_KEY, { renderer, scene, camera }) ile alt ağaca sunar. Child mesh bileşenleri inject ile köke erişir; her child'ın kendi WebGLRenderer'ı olmaz. Bu, Angular'daki root-provided servis veya R3F context'e benzer diplomatik hat: tek kök, çok dal.

Injection key sembol veya string olabilir; Holodepth önerisi: çakışmayı önlemek için Symbol('three-root') veya namespaced string. Provide yalnızca mount ağacında bir kez yapılır; nested ikinci provide ikinci kök yaratır ve «hangi scene?» tartışması başlatır.

TresJS tarzı kütüphaneler bu provide katmanını içeride çözer; ham Three'de siz yazarsınız. Kütüphane seçimi mount sözleşmesini değiştirmez: yine tek context, yine mount ömrü.

SSR ve ClientOnly kapısı

Nuxt / Vite SSR ortamında window, document ve WebGL yoktur. Holodepth: kök açılışı <ClientOnly> (Nuxt) veya import.meta.env.SSR / typeof window !== 'undefined' kapısı arkasındadır. Sunucuda renderer yaratmaya çalışmak build'i değil, hydration'ı kırar.

Hydration sonrası ilk frame: mount host boyutunun settle olmasını bekleyin; aksi 1×1 tampon riski. Bu Nuxt hydration dersi değil; Three mount zamanlamasıdır. SSR HTML'de canvas placeholder bırakılabilir; gerçek WebGL yalnızca istemcide açılır.

Dinamik import (defineAsyncComponent) ile Stage bileşenini istemciye erteleyen ekipler de aynı kapıyı farklı kapı koluyla geçer. Önemli olan: sunucu render yolunda Three modülünün yan etkisiz kalması.

Renderer mührü: GL imzası

Antialias, alpha, powerPreference, outputColorSpace, toneMapping ürün sabitleri mount açılışında bir kez mühürlenir. Her child'da rastgele değiştirmek «neden renk kaydı?» tartışması üretir. R3F'de bu gl / onCreated yüzüdür; Angular'da host factory; Vue'da aynı diplomatik karar mount bileşeni (veya provide edilen factory) üzerindedir.

Kare metronom ve watchEffect politikası Kare döngüsü & watchEffect yaprağındadır. Burada yalnız: renderer varoluş sözleşmesi mount ömrüne yazılır; requestAnimationFrame döngüsü mount'ta başlatılabilir ama reaktif bağ kuralları sonraki yaprakta.

Güvenli mount: simüle mühür

Örnekler öğretim amaçlı, güncel Vue 3 + Three yüzeyine yaslanan simüle kalıplardır. Pinia / Vuex veya build aracı seçimi öğretilmez. Paket sürümlerinizi doğrulayın.

Simüle · vitrin mount'u. Template ref; açılış onMounted; provide kök; kapanış onBeforeUnmount iskeleti.

<script setup>
import { onBeforeUnmount, onMounted, provide, ref, shallowRef } from 'vue'
import * as THREE from 'three'
import { THREE_ROOT_KEY } from './threeRootKey'

const hostRef = ref(null)
const renderer = shallowRef(null)
const scene = shallowRef(null)
const camera = shallowRef(null)
let resizeObs = null

onMounted(() => {
  // SSR: bu blok yalnızca istemcide çalışmalı (ClientOnly veya env kapısı).
  const host = hostRef.value
  if (!host) return

  const canvas = document.createElement('canvas')
  canvas.style.display = 'block'
  canvas.style.width = '100%'
  canvas.style.height = '100%'
  host.appendChild(canvas)

  const r = new THREE.WebGLRenderer({
    canvas,
    antialias: true,
    alpha: false,
    powerPreference: 'high-performance',
    preserveDrawingBuffer: false,
  })
  r.outputColorSpace = THREE.SRGBColorSpace
  r.toneMapping = THREE.ACESFilmicToneMapping

  const s = new THREE.Scene()
  const c = new THREE.PerspectiveCamera(50, 1, 0.1, 100)
  c.position.set(0, 0, 4)

  renderer.value = r
  scene.value = s
  camera.value = c

  provide(THREE_ROOT_KEY, { renderer: r, scene: s, camera: c })

  const applySize = () => {
    const w = host.clientWidth
    const h = host.clientHeight
    if (w < 2 || h < 2) return
    const dpr = Math.min(window.devicePixelRatio || 1, 2)
    r.setPixelRatio(dpr)
    r.setSize(w, h, false)
    c.aspect = w / h
    c.updateProjectionMatrix()
  }
  applySize()

  resizeObs = new ResizeObserver(() => applySize())
  resizeObs.observe(host)
})

onBeforeUnmount(() => {
  resizeObs?.disconnect()
  const r = renderer.value
  r?.dispose()
  renderer.value = null
  scene.value = null
  camera.value = null
  // Geometri / texture dispose borusu Yükleme & onBeforeUnmount yaprağında derinleşir.
})
</script>

<template>
  <div
    ref="hostRef"
    class="stage-root"
    style="width:100%;height:100%;min-height:360px;position:relative"
  />
</template>

Simüle · kök anahtarı ve sahiplik notu. inject tarafı child SFC'lerde kullanılır; birden fazla StageRoot varsa ikinci renderer'ı varsayılan yapmayın.

import { inject } from 'vue'

/** @typedef {{ renderer: import('three').WebGLRenderer, scene: import('three').Scene, camera: import('three').Camera }} ThreeRoot */

export const THREE_ROOT_KEY = Symbol('holodepth-three-root')

/** @type {'mount-local' | 'app-shared'} */
export const RENDERER_OWNERSHIP = 'mount-local'

export function useThreeRoot() {
  const root = inject(THREE_ROOT_KEY, null)
  if (!root) {
    throw new Error('[Holodepth] THREE_ROOT_KEY bulunamadı · StageRoot altında mısınız?')
  }
  return /** @type {ThreeRoot} */ (root)
}

export function assertSingleContext(liveRendererCount) {
  if (RENDERER_OWNERSHIP === 'mount-local' && liveRendererCount > 1) {
    console.warn('[Holodepth] Birden fazla canlı context · mobil bütçeyi kontrol edin.')
  }
}

Güvenlik: kullanıcıdan gelen canvas boyutlarını sınırsız şişirmeyin; DPR tavanı zorunlu. SSR'de native DOM API çağrılarını ClientOnly veya env kapısı olmadan çalıştırmayın. shallowRef ile renderer/scene'yi derin reaktif yapmayın; ağır nesneler proxy tuzağına düşer.

Büyütme, DPR ve ResizeObserver

Pencere veya yan panel açılınca mount host ölçüsü değişir. Holodepth varsayılanı: ResizeObserver ile setSize yolunu tetiklemek; ham window.resize dinleyicisi yalnızca bilinçli tercih. Her watch veya watchEffect turunda setSize çağırmak thrashing üretir; seyreltin.

devicePixelRatio tavanı (tipik 2) fill-rate bütçesini korur. Retina'da keskinlik isterken mobilde ısınma riski taşırsınız. DPR kararı mount mührünün parçasıdır; child bileşenlerde rastgele değiştirmeyin.

setSize(w, h, false) üçüncü argüman CSS boyutunu canvas stiline yansıtmaz; layout zaten host CSS'inden gelir. İki defter kuralı: CSS %100, backing store DPR ile çarpılır.

İki bağlam tuzağı

Her mount kartında yeni WebGLRenderer = yeni context. Dashboard'da üç küçük Vue bileşeni üç GPU oturumu açabilir. Holodepth varsayılanı: mümkün olduğunca tek canlı renderer; paylaşım gerekiyorsa provide kökü bilinçli tasarlanır, «her widget kendi Three'si» alışkanlığı yapılmaz.

İkinci tuzaq: aynı sayfada hem ham StageRoot hem TresJS <TresCanvas> denemesi. İki soyutlama katmanı iki context üretir. Ticket'ta sahiplik cümlesi yazın: renderer mount'ta mı, app-level composable'da mı?

Üçüncü tuzaq: keep-alive ile cache'lenen mount bileşeninde observer ve renderer'ı yeniden bağlamayı unutmak. Aktivasyon/deaktivasyon kancalarında ResizeObserver ve render döngüsü politikası net olmalı; aksi «donmuş kare» veya «çift observer» üretir.

Üç mount profili

  • 01 · Mount

    Ürün vitrini (ham Three)

    Sahiplik
    mount-local renderer + provide
    DPR
    Tavan 2 · keskinlik
    Risk
    İkinci rota ile ikinci StageRoot
  • 02 · Mount

    TresJS tarzı canvas

    Sahiplik
    Kütüphane iç provide · tek context
    Boyut
    Sabit min-height · ResizeObserver
    Risk
    Ham Stage ile çift canvas
  • 03 · Mount

    Nuxt SSR + ClientOnly

    Kapı
    ClientOnly / dynamic import
    Açılış
    onMounted + layout settle
    Risk
    Sunucuda WebGL denemesi

Mount tuzakları

  1. Erken ref: setup içinde hostRef.value null.
  2. 0 yükseklik: flex child min-height yok.
  3. Derin reaktif Three: ref(renderer) proxy thrash.
  4. Çoklu context: kart başı renderer veya çift canvas.
  5. SSR çağrısı: ClientOnly kapısız WebGL.
  6. watch thrash: her state'te setSize.
  7. Unmount yok: rota sonrası context sızıntısı.
  8. R3F ezberi: onCreated veya frameloop prop'u aramak.
  9. Angular ezberi: AfterViewInit / ElementRef beklemek.

Holodepth perspektifleri

Ömür = context

Renderer mount ömrüne yazılır. Orphan context mobilin sessiz düşmanıdır. onBeforeUnmount'ta dispose silüeti burada; geometri ve texture derinliği kargo yaprağındadır.

Bir context varsayılan

İkinci mount ikinci gerekçe ister; widget ızgarası gerekçe değildir. provide kökü paylaşım için tasarlanır, çoğaltma için değil.

İki defteri ayırın

CSS kutusu ile DPR tamponu aynı watch içinde konuşulmamalı. Layout CSS'te, backing store renderer'da kalır.

shallowRef disiplini

Scene, renderer ve mesh referansları derin reaktif olmamalı. Reaktif kaçış ihtiyacı Reaktif kaçış & performans yaprağında; mount'ta yalnız «ağır nesne proxy'lenmez» mührü.

Teşhis maddeleri

  1. Renderer onMounted'ta mı açılıyor?
  2. Host min-height / boyut sözleşmesi var mı?
  3. DPR tavanı yazılı mı?
  4. Canlı context sayısı gerekçeli mi?
  5. SSR ClientOnly kapısı var mı?
  6. ResizeObserver thrash yapıyor mu?
  7. onBeforeUnmount renderer'ı salıyor mu?
  8. provide/inject kökü tek mi?
  9. R3F, Angular host veya glTF şema bu sayfaya sızmış mı?

Holodepth içgörüsü

«Vue'da Three çalışmıyor» çoğu zaman mesh hatası değil; mount zamanlaması, sıfır kutu, shallowRef ihlali veya çoklu context sorunudur. Önce gümrük masasını okuyun; sonra harita → tik → kargo zincirine geçin.

Sonraki kapı: reaktif harita

Kapı mühürlendikten sonra Vue şablonu (veya TresJS yapı taşları) Object3D ağacına nasıl bağlanır? Sonraki sayfa Reaktif şablon → Object3D bu eşlemeyi anlatır: mount → harita adımı. watchEffect döngüsü, yükleme borusu, olay bağları ve reaktif kaçış sonraki yapraklardadır.

Zinciri hatırlayın: mount (burada) · harita (şablon grafiği) · tik (kare döngüsü) · kargo (yükleme ömrü) · bağ (picking) · kaçış (performans). Her halka ayrı yaprak; bu sayfa yalnızca ilk mührü taşır.