HTML-in-Canvas API kaynak denemesi tanıtımı

Thomas Nattestad
Thomas Nattestad

Web geliştiriciler, web'de karmaşık ve yüksek düzeyde etkileşimli görsel uygulamalar oluştururken yıllardır zorlu bir mimari seçim yapmak zorunda kalıyor: Zengin semantik özelliklerinden dolayı DOM'u mu kullanacaksınız yoksa düşük düzeyli grafik performansı için doğrudan <canvas> öğesine mi oluşturacaksınız?

Şu anda kaynak denemesinde kullanılabilen yeni deneysel HTML-in-Canvas API ile seçim yapmak zorunda değilsiniz. Bu API, kullanıcı arayüzünü etkileşimli, erişilebilir ve en sevdiğiniz tarayıcı özelliklerine bağlı tutarken DOM içeriğini doğrudan 2D tuvale veya WebGL/WebGPU dokusuna çizmenize olanak tanır. HTML'yi düşük düzeyli grafik işleme ile birleştirerek daha önce mümkün olmayan deneyimler oluşturabilirsiniz.

DOM ve Canvas

Bu yeni API'nin gücünü anlamak için hem DOM'un hem de Canvas'ın göreceli güçlü yönlerine bakmak faydalı olur.

DOM, web kullanıcı arayüzünün temelidir. Anlamsal olarak anlaşılan içerikleri kullanarak zengin arayüzler oluşturmak için kutudan çıktığı gibi metin düzeni çözümleri sunar. Bu sayede kullanıcılar, web sayfalarında sorunsuz bir şekilde yaygın işlemleri gerçekleştirebilir. Örneğin, kopyalamak için metni vurgulama veya kaydetmek için bir resmi sağ tıklama gibi, genellikle göz ardı ettiğimiz işlemler. DOM, temel tarayıcı özellikleriyle de entegre olur: erişilebilirlik araçları, çeviri, sayfa içi arama, okuyucu modu, uzantılar, koyu mod, tarayıcı yakınlaştırma ve otomatik doldurma.

Canvas (ve WebGL/WebGPU) ise son derece gelişmiş 2D ve 3D grafikler için bir piksel ızgarasını çalıştırmak üzere düşük düzeyde erişime olanak tanır. Oyunlar ve karmaşık web uygulamaları (ör. Google Dokümanlar veya Figma) bu yüksek performanslı, düşük düzeyli erişimi gerektirir. Tuval temelde bir piksel ızgarası olduğundan, duyarlı metin gibi özellikleri desteklemek için karmaşık özel kullanıcı arayüzü mantığı kullanılması gerekiyordu. Bu da paket boyutunuzu önemli ölçüde artırıyordu. En önemlisi, kullanıcı arayüzü statik bir tuval piksel ızgarasına sıkıştığında DOM'a entegre edilen tüm güçlü tarayıcı özellikleri tamamen bozulur.

DOM'u Canvas'a taşımanın avantajları

Tuvalde HTML API'si, her iki dünyanın en iyi özelliklerini bir araya getiren bir köprüdür. HTML'yi <canvas> öğesinin içine yerleştirip dönüşümünü senkronize ederek içeriğin tamamen etkileşimli kalmasını ve tüm tarayıcı entegrasyonlarının otomatik olarak çalışmasını sağlarsınız.

DOM'un kullanıcı arayüzünüzü bir <canvas> öğesi içinde işlemesine izin vererek elde edeceğiniz avantajlar:

  • Metin düzeni ve biçimlendirme: CSS stilleri uygulanmış çok satırlı veya çift yönlü metin de dahil olmak üzere basitleştirilmiş metin düzeni ve biçimlendirme.
  • Form kontrolleri: Kapsamlı özelleştirme seçenekleriyle etkileyici ve kullanımı daha kolay form kontrolleri.
  • Metin seçme, kopyalama/yapıştırma ve sağ tıklama: Kullanıcılar, 3D sahnelerinizdeki metinleri vurgulayabilir veya içerik menülerini sağ tıklayabilir.
  • Metin seçme, kopyalama/yapıştırma ve sağ tıklama: Kullanıcılar, 3D sahnelerinizdeki metinleri vurgulayabilir veya içerik menülerini sağ tıklayabilir.
  • Erişilebilirlik: Tuvalin içinde oluşturulan içerik, erişilebilirlik ağacına sunulur. Erişilebilirlik sistemleri, kullanıcı arayüzünü normal HTML gibi ayrıştırabilir ve ekran okuyucular gibi sistemlere sunabilir.
  • Find-in-page: Kullanıcılar metin aramak için sayfada bulma (Ctrl/Cmd+F) özelliğini kullanabilir. Tarayıcı, metni doğrudan WebGL dokularınızda vurgular.
  • Find-in-page: Kullanıcılar metin aramak için sayfada bulma (Ctrl/Cmd+F) özelliğini kullanabilir. Tarayıcı, metni doğrudan WebGL dokularınızda vurgular.
  • Dizinlenebilirlik ve yapay zeka ajanıyla arayüz oluşturma: Web tarayıcıları ve yapay zeka ajanları, 2D ve 3D sahnelerinizde oluşturulan metni sorunsuz bir şekilde dizine ekleyip okuyabilir.
  • Uzantı entegrasyonu: Tarayıcı uzantıları yerel olarak çalışır. Örneğin, metin değiştirme uzantısı, 3D ağlarınızda oluşturulan metni otomatik olarak günceller.
  • Geliştirici Araçları entegrasyonu: WebGL/WebGPU kullanıcı arayüzü öğeleri de dahil olmak üzere tuval içeriğinizi doğrudan Chrome Geliştirici Araçları'nda inceleyebilirsiniz. İnceleyicide bir CSS stilini ayarlayın ve 3D dokuda anında güncellendiğini görün.

Karmaşık kullanım alanları

Bu API, çeşitli alanlarda inanılmaz bir potansiyel sunar:

  • Geniş tuval tabanlı uygulamalar: Google Dokümanlar, Miro veya Figma gibi ağır web uygulamaları artık karmaşık uygulama kullanıcı arayüzü bileşenlerini tuval tabanlı çalışma alanlarında yerel olarak oluşturarak erişilebilirliği artırabilir ve paket ağırlığını azaltabilir.
  • 3D sahneler ve oyunlar: Pazarlama siteleri, sürükleyici WebXR deneyimleri ve web oyunları artık 3D sahnelerde tamamen etkileşimli web kullanıcı arayüzü yerleştirebilir. Örneğin, gerçek DOM metni kullanan bir 3D kitap veya kopyalama ve yapıştırmayı doğal olarak destekleyen bir oyun içi terminal.

API nasıl kullanılır?

API kullanımı üç aşamada gerçekleşir: tuvalinizi ayarlama, tuvalde oluşturma ve CSS dönüşümünü güncelleme (böylece tarayıcı, öğenin ekranda fiziksel olarak nerede bulunduğunu bilir).

Ön koşullar

HTML-in-Canvas API, Chrome 148-150 sürümlerinde kaynak denemesi aşamasındadır. Sitenizde test etmek için chrome://flags/#canvas-draw-element flag'i etkinleştirilmiş Chrome Canary 149 veya sonraki sürümleri kullanın. API'yi diğer kullanıcılar için etkinleştirmek istiyorsanız deneme sürecine kaydolun.

1. adım: Temel Canvas kurulumu

Öncelikle layoutsubtree özelliğini <canvas> etiketinize ekleyin. Bu, tarayıcının tuvalin içine yerleştirilmiş içerikten haberdar olmasını sağlar, içeriği tuvalin içinde gösterilmeye hazırlar ve erişilebilirlik ağaçlarına sunar.

<canvas id="canvas" style="width: 200px; height: 200px;" layoutsubtree>
  <div id="form_element">
    <label for="name">Name:</label> <input id="name" type="text">
  </div>
</canvas>

Kanvas ızgarasını boyutlandırma

Oluşturulan içeriğin bulanık görünmesini önlemek için tuval ızgarasını cihaz ölçeklendirme faktörüne uygun şekilde boyutlandırdığınızdan emin olun.

const observer = new ResizeObserver(([entry]) => {
  const dpc = entry.devicePixelContentBoxSize;
  canvas.width = dpc ? dpc[0].inlineSize : Math.round(entry.contentRect.width * window.devicePixelRatio);
  canvas.height = dpc ? dpc[0].blockSize : Math.round(entry.contentRect.height * window.devicePixelRatio);
});

const supportsDevicePixelContentBox =
  typeof ResizeObserverEntry !== 'undefined' &&
  'devicePixelContentBoxSize' in ResizeObserverEntry.prototype;
const options = supportsDevicePixelContentBox ? { box: 'device-pixel-content-box' } : {};
observer.observe(canvas, options);

2. adım: Oluşturma

2D bağlam için drawElementImage yöntemini kullanın. Bunu, öğe yeniden çizildiğinde (ör. metin vurgulama veya kullanıcı girişi sırasında) tetiklenen paint etkinliğinin içinde yapın. Etkileşimin çalışmaya devam etmesi için öğenin CSS dönüşümünü dönüş değeriyle güncellemek çok önemlidir.

const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');

canvas.onpaint = () => {
  ctx.reset();

  // Draw the form element at x:0, y:0
  let transform = ctx.drawElementImage(form_element, 0, 0);

  // Use the transform returned later on...
};

WebGL ile oluşturma

WebGL için texElementImage2D kullanırsınız. texImage2D işlevine benzer ancak kaynak olarak DOM öğesini alır.

canvas.onpaint = () => {
  if (gl.texElementImage2D) {
    gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, form_element);
  }
};

WebGPU ile oluşturma

WebGPU, cihaz sırasındaki copyElementImageToTexture yöntemini kullanır. Bu yöntem, copyExternalImageToTexture yöntemine benzer:

canvas.onpaint = () => {
  root.device.queue.copyElementImageToTexture(
    valueElement,
    { texture: targetTexture }
  );
};

3. adım: CSS dönüşümünü güncelleyin

Öğeyi tuvale yerleştirdiğinize göre tarayıcıyı öğenin konumu hakkında güncellemeniz gerekir. Bu, tuval ile DOM'un düzeni arasında mekansal senkronizasyon sağlar. Tarayıcının, etkinlik bölgesini (ör. kullanıcının tam olarak nereye tıkladığı veya fareyle nereye geldiği) öğenin oluşturulduğu yerle doğru şekilde eşleyebilmesi için bu önemlidir.

2D bağlamı için oluşturma çağrısı tarafından döndürülen dönüşümü .style.transform property öğesine uygulayın:

const ctx = document.getElementById('canvas').getContext('2d');
const form_element = document.getElementById('form_element');
const canvas = document.getElementById('canvas');

canvas.onpaint = () => {
  ctx.reset();
  // Draw the form element at x:0, y:0
  let transform = ctx.drawElementImage(form_element, 0, 0);

  // Sync the DOM location with the drawn location
  form_element.style.transform = transform.toString();
};

WebGL veya WebGPU ile bir öğenin ekrandaki konumu, çıkış dokusunun gölgelendirici kodu tarafından nasıl kullanıldığına bağlıdır ve tuval oluşturma bağlamından çıkarılamaz. Ancak gölgelendirici programınız dokuyu çizmek için tipik bir model görünümü projeksiyonu kullanıyorsa element.getElementTransform() işlevinden döndürülen değerle aynı şekilde kullanılabilecek bir dönüşüm hesaplamak için yeni kolaylık işlevi drawElementImage()'yi kullanabilirsiniz. Bu işlemi kolaylaştırmak için aşağıdakileri yapmanız gerekir:

  • WebGL MVP Matrisi'ni DOM Matrisi'ne dönüştürün.
  • HTML öğesini normalleştirin. HTML öğeleri piksel cinsinden boyutlandırılır (örneğin, 200 piksel genişliğinde). Ancak WebGL genellikle nesneleri 0 ile 1 arasında değişen "birim kareler" olarak ele alır. Normalleştirme yapmazsanız 200 piksel boyutundaki düğmeniz 200 kat daha büyük görünür.
  • Tuval görüntü alanıyla eşleyin. Bu adım, "yeniden ölçeklendirme" aşamasıdır: Bu aşamada, birim alanı matematiği, ekrandaki <canvas> öğenizin gerçek piksel boyutlarıyla eşleşecek şekilde tekrar genişletilir. Ayrıca, WebGL'de yukarı pozitif, CSS'de ise aşağı pozitif olduğundan Y eksenini de ters çevirir.
  • Son dönüşümü hesaplayın. Matrisleri sırayla çarpın: Viewport * MVP * Normalization. Bunları tek bir nihai dönüşümde birleştirmek, tarayıcıya 3D çizimle hizalanmak için HTML öğesi katmanının tam olarak nerede bulunması gerektiğini söyleyen bir "harita" oluşturur.
  • Dönüşümü HTML öğesine uygulayın. Bu işlem, HTML öğesi katmanını oluşturulan piksellerin doğrudan üzerine taşır. Bu, kullanıcının bir düğmeyi tıkladığında veya metin seçtiğinde gerçek HTML öğesine dokunmasını sağlar.
if (canvas.getElementTransform) {
  // 1. Convert WebGL MVP Matrix to DOM Matrix
  const mvpDOM = new DOMMatrix(Array.from(htmlElementMVP));

  // 2. Normalize the HTML element (pixels -> 1x1 unit square)
  const width = targetHTMLElement.offsetWidth;
  const height = targetHTMLElement.offsetHeight;

  const cssToUnitSpace = new DOMMatrix()
    .scale(1 / width, -1 / height, 1) // Shrink to unit size and flip Y
    .translate(-width / 2, -height / 2); // Center the element

  // 3. Map to the canvas viewport
  const clipToCanvasViewport = new DOMMatrix()
    .translate(canvas.width / 2, canvas.height / 2) // Move origin to center
    .scale(canvas.width / 2, -canvas.height / 2, 1); // Stretch to canvas dimensions

  // 4. Multiply: (Clip -> Pixels) * (MVP) * (pixels -> unit square)
  const screenSpaceTransform = clipToCanvasViewport
      .multiply(mvpDOM)
      .multiply(cssToUnitSpace);

  // 5. Apply to the transform
  const computedTransform = canvas.getElementTransform(targetHTMLElement, screenSpaceTransform);
  if (computedTransform) {
    targetHTMLElement.style.transform = computedTransform.toString();
  }
}

Kitaplık ve çerçeve desteği

Popüler kitaplıklardan bazıları, tuvalde HTML özelliği için destek sunmaya başladı.

Three.js

Matrisleri manuel olarak güncellemek sıkıcı olabilir. Bu nedenle, çerçeveler bu işe şimdiden dahil oluyor. Three.js, yeni THREE.HTMLTexture kullanılarak deneysel destek sunar:

const material = new THREE.MeshBasicMaterial();
material.map = new THREE.HTMLTexture(uiElement); // Pass the DOM element

const geometry = new THREE.BoxGeometry(1, 1, 1);
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);

PlayCanvas

PlayCanvas, doku API'sini kullanarak Canvas'ta HTML'yi de destekler:

// Wait for the 'paint' event to set the source
canvas.addEventListener('paint', () => {
    htmlTexture.setSource(htmlElement);
}, { once: true });
canvas.requestPaint();

// Keep up to date
canvas.addEventListener('paint', onPaintUpload);

const material = new pc.StandardMaterial();
material.diffuseMap = htmlTexture;
material.update();

Demolar

Demoları denemeden önce ortamınızın doğru şekilde yapılandırıldığından emin olun.

API kullanımına referans olarak hizmet veren çeşitli demolar vardır. Topluluktan, çevrilebilir 3D kitaplardan cam gölgelendiricilerde kırılan kullanıcı arayüzü öğelerine kadar çeşitli yaratıcı çözümler görüyoruz:

  • 3D kitap: Sayfaları için HTML düzeni kullanan, WebGL ile oluşturulmuş 3D kitap. Kullanıcılar, CSS ile yazı tiplerini değiştirebilir. DOM tabanlı olduğundan yerleşik çeviri anında çalışır ve yapay zeka aracıları metni daha az karmaşıklıkla ayıklayabilir.
  • Etkileşimli 3D kullanıcı arayüzleri: Alttaki 3D modele göre ışığı kıran ve standart HTML <input type="range"> adım özelliklerine yanıt vermeye devam eden bir WebGPU jöle kaydırıcı.
  • Animasyonlu dokular: Özel bir animasyon döngüsüne gerek kalmadan, DOM'u doğrudan bir WebGL dokusuna kullanarak animasyonlu bir SVG kalem oluşturan dinamik bir 3D reklam panosu.
  • Kırılma katmanları: Hareket eden bir 3D imleç tarafından bozulmuş, ancak sayfada bulma özelliği kullanılarak tamamen seçilebilir ve aranabilir etkileşimli bir tipografi katmanı.

Topluluk tarafından oluşturulan demo koleksiyonuna göz atın. Canvas'ta HTML demosunun bu koleksiyonda yer almasını istiyorsanız eklemek için çekme isteği oluşturun.

Sınırlamalar

API güçlü olsa da bilinçli olarak belirlenmiş birkaç sınırlaması vardır:

  • Kaynaklar arası içerik: API, güvenlik ve gizlilik nedenleriyle kaynaklar arası iframe içeriğiyle çalışmaz.
  • Ana iş parçacığında kaydırma: Tuvaldeki HTML, JavaScript ile çizilir. Bu nedenle, kaydırma ve animasyonlar, tuvalin dışında olduğu gibi JavaScript'ten bağımsız olarak güncellenemez. Geliştiriciler, kaydırılan içeriği tuvalin içine yerleştirmenin veya tuvalin tamamını kaydırmanın performans özelliklerini dikkatlice değerlendirmelidir.

Geri bildirim

Canvas API'de HTML ile denemeler yapıyorsanız görüşlerinizi bizimle paylaşın. API tasarımına yön vermemize yardımcı olmak için deneysel aşamada olan bu özelliği sitenizde etkinleştirmek üzere kaynağı deneme programına kaydolabilirsiniz. Geri bildirimde bulunmak için sorun da bildirebilirsiniz.

Kaynaklar