Aydınlatmalı öğe içeren bir tuğla görüntüleyici oluşturma

1. Giriş

Web bileşenleri

Web bileşenleri, geliştiricilerin HTML'yi özel öğelerle genişletmesine olanak tanıyan web standartları koleksiyonudur. Bu codelab'de, tuğla modelleri görüntüleyebilen <brick-viewer> öğesini tanımlayacaksınız.

ışık-öğesi

<brick-viewer> özel öğemizi tanımlamamıza yardımcı olmak için "lit-element" ifadesini kullanacağız. lit-element, web bileşenleri standardına sentetik şeker ekleyen hafif bir temel sınıftır. Bu yöntem, özel öğemizi kullanmaya başlamamızı kolaylaştıracaktır.

Başlayın

Online Stackblitz ortamında kod yazacağız, bu nedenle şu bağlantıyı yeni bir pencerede açın:

stackblitz.com/edit/brick-viewer

Haydi, başlayalım.

2. Özel Öğe Tanımlayın

Sınıf tanımı

Özel öğe tanımlamak için LitElement öğesini genişleten bir sınıf oluşturun ve bunu @customElement ile süsleyin. @customElement bağımsız değişkeni, özel öğenin adı olacaktır.

brick-viewer.ts içine şunu koyun:

@customElement('brick-viewer')
export class BrickViewer extends LitElement {
}

Artık <brick-viewer></brick-viewer> öğesi, HTML'de kullanılmaya hazırdır. Ancak denerseniz hiçbir şey oluşturulmaz. Gelin bu sorunu çözelim.

Oluşturma yöntemi

Bileşenin görünümünü uygulamak için oluşturucu adlı bir yöntem tanımlayın. Bu yöntem, html işleviyle etiketlenmiş bir değişmez şablon döndürmelidir. Etiketli şablon değişmez değerine istediğiniz HTML'yi girin. Bu, <brick-viewer> kullandığınızda oluşturulur.

render yöntemini ekleyin:

export class BrickViewer extends LitElement {
  render() {
    return html`<div>Brick viewer</div>`;
  }
}

3. LDraw Dosyasını Belirtme

Bir mülk tanımlayın

Bir <brick-viewer> kullanıcısı, aşağıdaki gibi bir özellik kullanarak hangi tuğla modelinin gösterileceğini belirtebilse harika olurdu:

<brick-viewer src="path/to/model.ldraw"></brick-viewer>

Bir HTML öğesi oluşturduğumuz için bildirim temelli API'den yararlanabilir ve <img> veya <video> etiketinde olduğu gibi bir kaynak özelliği tanımlayabiliriz. Işıklandırma ile sınıfınızı @property ile dekore etmek kadar kolay. type seçeneği, "lit" öğesinin özelliği bir HTML özelliği olarak kullanmak üzere nasıl ayrıştıracağını belirtebilmenizi sağlar.

src özelliğini ve özelliğini tanımlayın:

export class BrickViewer extends LitElement {
  @property({type: String})
  src: string|null = null;
}

<brick-viewer> artık HTML'de ayarlayabileceğimiz bir src özelliğine sahip. Değeri, lit-element sayesinde BrickViewer sınıfımızdan okunabiliyor.

Değerleri görüntüleme

src özelliğinin değerini, oluşturma yönteminin şablon değişmez değerinde kullanarak görüntüleyebiliriz. ${value} söz dizimini kullanarak değerleri şablon değişmez değerlerine dönüştürün.

export class BrickViewer extends LitElement {
  render() {
    return html`<div>Brick model: ${this.src}</div>`;
  }
}

Şimdi, src özelliğinin değerini penceredeki <brick-viewer> öğesinde görüyoruz. Şunu deneyin: Tarayıcınızın geliştirici araçlarını açın ve src özelliğini manuel olarak değiştirin. Haydi, deneyin...

Öğedeki metnin otomatik olarak güncellendiğini fark ettiniz mi? lit-element, @property ile dekore edilmiş sınıf özelliklerini gözlemler ve görünümü sizin için yeniden oluşturur. işin kendisinde işin işini yapar, böylece sizin uğraşmanıza gerek kalmaz.

4. Three.js ile sahneyi oluşturun

Işık, Kamera, Oluşturma!

Özel öğemiz, 3D tuğla modellerimizi oluşturmak içinthree.js kullanır. Bir <brick-viewer> öğesinin her örneği için yalnızca bir kez yapmak istediğimiz şeyler var. Örneğin,Three.js sahnesini, kamerayı ve ışıklandırmayı ayarlayabiliriz. Bunları, BrickViewer sınıfına oluşturucuya ekleyeceğiz. Daha sonra kullanabilmeniz için bazı nesneleri sınıf özellikleri olarak tutacağız: kamera, sahne, denetimler ve oluşturucu.

Üç.js sahne kurulumunu ekleyin:

export class BrickViewer extends LitElement {

  private _camera: THREE.PerspectiveCamera;
  private _scene: THREE.Scene;
  private _controls: OrbitControls;
  private _renderer: THREE.WebGLRenderer;

  constructor() {
    super();

    this._camera = new THREE.PerspectiveCamera(45, this.clientWidth/this.clientHeight, 1, 10000);
    this._camera.position.set(150, 200, 250);

    this._scene = new THREE.Scene();
    this._scene.background = new THREE.Color(0xdeebed);

    const ambientLight = new THREE.AmbientLight(0xdeebed, 0.4);
    this._scene.add( ambientLight );

    const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
    directionalLight.position.set(-1000, 1200, 1500);
    this._scene.add(directionalLight);

    this._renderer = new THREE.WebGLRenderer({antialias: true});
    this._renderer.setPixelRatio(window.devicePixelRatio);
    this._renderer.setSize(this.offsetWidth, this.offsetHeight);

    this._controls = new OrbitControls(this._camera, this._renderer.domElement);
    this._controls.addEventListener("change", () =>
      requestAnimationFrame(this._animate)
    );

    this._animate();

    const resizeObserver = new ResizeObserver(this._onResize);
    resizeObserver.observe(this);
  }

  private _onResize = (entries: ResizeObserverEntry[]) => {
    const { width, height } = entries[0].contentRect;
    this._renderer.setSize(width, height);
    this._camera.aspect = width / height;
    this._camera.updateProjectionMatrix();
    requestAnimationFrame(this._animate);
  };

  private _animate = () => {
    this._renderer.render(this._scene, this._camera);
  };
}

WebGLRenderer nesnesi, oluşturulanThree.js sahnesini gösteren bir DOM öğesi sağlar. Bu mülke domElement mülkü üzerinden erişilir. Bu değeri, ${value} söz dizimini kullanarak oluşturma şablonu sabit değerine ayarlayabiliriz.

Şablonda bulunan src mesajını kaldırın ve oluşturucunun DOM öğesini ekleyin:

export class BrickViewer extends LitElement {
  render() {
    return html`
      ${this._renderer.domElement}
    `;
  }
}

Oluşturucunun dom öğesinin tamamının gösterilmesini sağlamak için <brick-viewer> öğesinin kendisini de display: block olarak ayarlamamız gerekir. Stilleri, css şablon sabit değerine ayarlanmış styles adlı bir statik mülkte sağlayabiliriz.

Sınıfa şu stili ekleyin:

export class BrickViewer extends LitElement {
  static styles = css`
    /* The :host selector styles the brick-viewer itself! */
    :host {
      display: block;
    }
  `;
}

Şu anda <brick-viewer>, oluşturulmuş birThree.js sahnesi gösteriyor:

Oluşturulmuş ancak boş bir sahne gösteren tuğla görüntüleyici öğesi.

Ama... boş. Şimdi bir model ekleyelim.

Tuğla yükleyici

Daha önce tanımladığımız src özelliğini, three.js ile birlikte gönderilen LDrawLoader'a ileteceğiz.

LDraw dosyaları, Tuğla modelini ayrı bina adımlarına ayırabilir. Toplam adım sayısına ve tuğla görünürlüğüne LDrawLoader API'si üzerinden erişilebilir.

Bu özellikleri, yeni _loadModel yöntemini ve oluşturucudaki yeni satırı kopyalayın:

@customElement('brick-viewer')
export class BrickViewer extends LitElement {
  private _loader = new LDrawLoader();
  private _model: any;
  private _numConstructionSteps?: number;
  step?: number;

  constructor() {
    // ...
    // Add this line right before this._animate();
    (this._loader as any).separateObjects = true;
    this._animate();
  }

  private _loadModel() {
    if (this.src === null) {
      return;
    }
    this._loader
        .setPath('')
        // Using our src property!
        .load(this.src, (newModel) => {

          if (this._model !== undefined) {
            this._scene.remove(this._model);
            this._model = undefined;
          }

          this._model = newModel;

          // Convert from LDraw coordinates: rotate 180 degrees around OX
          this._model.rotation.x = Math.PI;
          this._scene.add(this._model);

          this._numConstructionSteps = this._model.userData.numConstructionSteps;
          this.step = this._numConstructionSteps!;

          const bbox = new THREE.Box3().setFromObject(this._model);
          this._controls.target.copy(bbox.getCenter(new THREE.Vector3()));
          this._controls.update();
          this._controls.saveState();
        });
  }
}

_loadModel ne zaman aransın? src özelliği her değiştiğinde çağrılması gerekir. src mülkünü @property ile dekore ederek mülkü ışık öğe güncelleme yaşam döngüsüne dahil ettik. Bu dekore edilmiş mülklerden biri Değer değişikliklerinde, özelliklerin yeni ve eski değerlerine erişebilen bir dizi yöntem çağrılır. İlgilendiğimiz yaşam döngüsü yönteminin adı update. update yöntemi, az önce değişen özellikler hakkında bilgi içeren bir PropertyValues bağımsız değişkeni alır. Burası _loadModel işletmesini aramak için mükemmel bir yer.

update yöntemini ekleyin:

export class BrickViewer extends LitElement {
  update(changedProperties: PropertyValues) {
    if (changedProperties.has('src')) {
      this._loadModel();
    }
    super.update(changedProperties);
  }
}

<brick-viewer> öğemiz, artık src özelliğiyle belirtilen bir tuğla dosyasını görüntüleyebilir.

Bir araba modelinin gösterildiği tuğla görünümlü öğe.

5. Kısmi Modelleri Görüntüleme

Şimdi mevcut inşaat adımını yapılandırılabilir hale getirelim. <brick-viewer step="5"></brick-viewer> değerini belirtebilmek ve 5. inşaat adımında tuğla modelinin neye benzediğini görmek istiyoruz. Bunun için step özelliğini @property ile süsleyerek gözlemlenen bir mülk yapalım.

step mülkünü dekore edin:

export class BrickViewer extends LitElement {
  @property({type: Number})
  step?: number;
}

Şimdi, yalnızca geçerli yapı adımına kadar olan tuğlaları görünür hale getiren yardımcı bir yöntem ekleyeceğiz. Güncelleme yönteminde yardımcıyı, step özelliği her değiştirildiğinde çalışması için çağırırız.

update yöntemini güncelleyin ve yeni _updateBricksVisibility yöntemini ekleyin:

export class BrickViewer extends LitElement {
  update(changedProperties: PropertyValues) {
    if (changedProperties.has('src')) {
      this._loadModel();
    }
    if (changedProperties.has('step')) {
      this._updateBricksVisibility();
    }
    super.update(changedProperties);
  }

  private _updateBricksVisibility() {
    this._model && this._model.traverse((c: any) => {
      if (c.isGroup && this.step) {
        c.visible = c.userData.constructionStep <= this.step;
      }
    });
    requestAnimationFrame(this._animate);
  }
}

Tamam, şimdi tarayıcınızın geliştirici araçlarını açın ve <brick-viewer> öğesini inceleyin. Bu özelliğe step özelliği ekleyin. Örneğin:

Adım özelliği 10 olarak ayarlanmış, tuğla görünümlü bir öğenin HTML kodu.

Oluşturulan modele ne olduğunu izleyin. Modelin ne kadarının gösterileceğini kontrol etmek için step özelliğini kullanabiliriz. step özelliği "10" olarak ayarlandığında kod aşağıdaki gibi görünür:

Sadece on inşaat basamağı olan bir tuğla modeli.

6. Tuğla Sette Gezinme

mwc-simgesi-düğmesi

<brick-viewer> uygulamamızın son kullanıcısı da kullanıcı arayüzü üzerinden derleme adımlarında gezinebilir. Sonraki adım, önceki adım ve ilk adıma gitmek için düğmeler ekleyelim. Bunu kolaylaştırmak için Materyal Tasarım'ın düğme web bileşenini kullanacağız. @material/mwc-icon-button zaten içe aktarıldığından <mwc-icon-button></mwc-icon-button> içinde aktarmaya hazırız. Kullanmak istediğimiz simgeyi şu şekilde simge özelliğiyle belirtebiliriz: <mwc-icon-button icon="thumb_up"></mwc-icon-button>. Olası tüm simgeleri burada bulabilirsiniz: material.io/resources/icons.

Oluşturma yöntemine birkaç simge düğmesi ekleyelim:

export class BrickViewer extends LitElement {
  render() {
    return html`
      ${this._renderer.domElement}
      <div id="controls">
        <mwc-icon-button icon="replay"></mwc-icon-button>
        <mwc-icon-button icon="navigate_before"></mwc-icon-button>
        <mwc-icon-button icon="navigate_next"></mwc-icon-button>
      </div>
    `;
  }
}

Sayfamızda Materyal Tasarım'ı kullanmak, web bileşenleri sayesinde bu kadar kolay!

Etkinlik bağlantıları

Bu düğmelerin bir işlevi vardır. "Yanıt" düğmesi yapım adımını 1 olarak sıfırlayacaktır. "Gezinme_before" düğmesi, oluşturma adımını ve "navigation_next" öğesini azaltacaktır. değeri artırmalıdır. lit-element etkinlik bağlamalarıyla bu işlevi eklemeyi kolaylaştırır. Değişmez HTML şablonunuzda öğe özelliği olarak @eventname=${eventHandler} söz dizimini kullanın. eventHandler artık bu öğede bir eventname etkinliği algılandığında çalışacak. Örnek olarak, üç simge düğmemize tıklama etkinliği işleyicileri ekleyelim:

export class BrickViewer extends LitElement {
  private _restart() {
    this.step! = 1;
  }

  private _stepBack() {
    this.step! -= 1;
  }

  private _stepForward() {
    this.step! += 1;
  }

  render() {
    return html`
      ${this._renderer.domElement}
      <div id="controls">
        <mwc-icon-button @click=${this._restart} icon="replay"></mwc-icon-button>
        <mwc-icon-button @click=${this._stepBack} icon="navigate_before"></mwc-icon-button>
        <mwc-icon-button @click=${this._stepForward} icon="navigate_next"></mwc-icon-button>
      </div>
    `;
  }
}

Düğmeleri şimdi tıklamayı deneyin. İyi iş çıkardınız!

Stiller

Düğmeler çalışıyor ancak iyi görünmüyorlar. Hepsi en altta bir arada toplanır. Sahnede üst üste bindirecek şekilde stilize edelim.

Bu düğmelere stil uygulamak için static styles özelliğine geri döneriz. Bu stiller, yalnızca bu web bileşenindeki öğelere uygulanacağı anlamına gelir. Bu, web bileşenlerini yazmanın zevklerinden biridir: Seçiciler daha basit olabilir, CSS'nin okunması ve yazılması daha kolay olur. Hoşça kalın BEM!

Stilleri şu şekilde görünecek şekilde güncelleyin:

export class BrickViewer extends LitElement {
  static styles = css`
    :host {
      display: block;
      position: relative;
    }
    #controls {
      position: absolute;
      bottom: 0;
      width: 100%;
      display: flex;
    }
  `;
}

Yeniden başlatma, geri ve ileri düğmeleri bulunan bir tuğla izleyici öğesi.

Kamerayı sıfırla düğmesi

<brick-viewer> uygulamamızın son kullanıcıları, fare kontrollerini kullanarak sahneyi döndürebilir. Düğmeler eklerken kamerayı varsayılan konumuna sıfırlamak için bir düğme ekleyelim. Tıklama etkinliği bağlaması olan başka bir <mwc-icon-button> işi halleder.

export class BrickViewer extends LitElement {
  private _resetCamera() {
    this._controls.reset();
  }

  render() {
    return html`
      <div id="controls">
        <!-- ... -->
        <!-- Add this button: -->
        <mwc-icon-button @click=${this._resetCamera} icon="center_focus_strong"></mwc-icon-button>
      </div>
    `;
  }
}

Daha hızlı gezinme

Bazı tuğla setlerinde çok sayıda basamak vardır. Kullanıcı belirli bir adıma atlamak isteyebilir. Adım numaraları içeren bir kaydırma çubuğu eklemek hızlı gezinmeye yardımcı olabilir. Bunun için <mwc-slider> öğesini kullanacağız.

mwc-kaydırma çubuğu

Kaydırma çubuğu öğesinde, minimum ve maksimum kaydırma çubuğu değeri gibi birkaç önemli veri parçası gerekir. Minimum kaydırma çubuğu değeri her zaman "1" olabilir. Model yüklenmişse maksimum kaydırma çubuğu değeri this._numConstructionSteps olmalıdır. <mwc-slider> adlı çocuğa, özellikleri aracılığıyla bu değerleri söyleyebiliriz. _numConstructionSteps özelliği tanımlanmamışsa max özelliğini ayarlamaktan kaçınmak için ifDefined lit-html yönergesini de kullanabiliriz.

"Geri" ile "geri" arasında bir <mwc-slider> ekleyin ve "forward" düğmeler:

export class BrickViewer extends LitElement {
  render() {
    return html`
      <div id="controls">
        <!-- ... backwards button -->
        <!-- Add this slider: -->
        <mwc-slider
            step="1"
            pin
            markers
            min="1"
            max=${ifDefined(this._numConstructionSteps)}
        ></mwc-slider>
        <!-- ... forwards button -->
      </div>
    `;
  }
}

Veri "yukarıda"

Kullanıcı kaydırma çubuğunu hareket ettirdiğinde mevcut yapım adımı değişmeli ve modelin görünürlüğü buna uygun şekilde güncellenmelidir. Kaydırma çubuğu sürüklendiğinde, kaydırma çubuğu öğesi bir giriş etkinliği yayınlar. Bu etkinliği yakalamak ve oluşturma adımını değiştirmek için kaydırma çubuğuna bir etkinlik bağlama ekleyin.

Etkinlik bağlamayı ekleyin:

export class BrickViewer extends LitElement {
  render() {
    return html`
      <div id="controls">
        <!-- ...  -->
        <!-- Add the @input event binding: -->
        <mwc-slider
            ...
            @input=${(e: CustomEvent) => this.step = e.detail.value}
        ></mwc-slider>
        <!-- ... -->
      </div>
    `;
  }
}

Harika! Hangi adımın görüntüleneceğini değiştirmek için kaydırma çubuğunu kullanabiliriz.

Veri "aşağı"

Bir şey daha var. "Geri" ve "next" adımları değiştirmek için kaydırma çubuğu tutma yerinin güncellenmesi gerekir. <mwc-slider> öğesinin değer özelliğini this.step öğesine bağlayın.

value bağlamasını ekleyin:

export class BrickViewer extends LitElement {
  render() {
    return html`
      <div id="controls">
        <!-- ...  -->
        <!-- Add the value property binding: -->
        <mwc-slider
            ...
            value=${ifDefined(this.step)}
        ></mwc-slider>
        <!-- ... -->
      </div>
    `;
  }
}

Kaydırma çubuğu neredeyse bitmek üzere. Diğer kontrollerle güzel görünmesi için esnek bir stil ekleyin:

export class BrickViewer extends LitElement {
  static styles = css`
    /* ... */
    mwc-slider {
      flex-grow: 1;
    }
  `;
}

Ayrıca, kaydırma çubuğu öğesinin kendisinde layout değerini çağırmamız gerekir. Bu işlemi, DOM ilk kez oluşturulduktan sonra çağrılan firstUpdated yaşam döngüsü yönteminde gerçekleştireceğiz. query tasarımcısı, şablondaki kaydırma çubuğu öğesi için referans almamıza yardımcı olabilir.

export class BrickViewer extends LitElement {
  @query('mwc-slider')
  slider!: Slider|null;

  async firstUpdated() {
    if (this.slider) {
      await this.slider.updateComplete
      this.slider.layout();
    }
  }
}

Bir araya getirilen tüm kaydırma çubuğu eklemeleri (daha iyi görünmesini sağlamak için kaydırma çubuğunda ekstra pin ve markers özellikleriyle birlikte) aşağıda verilmiştir:

export class BrickViewer extends LitElement {
 @query('mwc-slider')
 slider!: Slider|null;

 static styles = css`
   /* ... */
   mwc-slider {
     flex-grow: 1;
   }
 `;

 async firstUpdated() {
   if (this.slider) {
     await this.slider.updateComplete
     this.slider.layout();
   }
 }

 render() {
   return html`
     ${this._renderer.domElement}
     <div id="controls">
       <mwc-icon-button @click=${this._restart} icon="replay"></mwc-icon-button>
       <mwc-icon-button @click=${this._stepBack} icon="navigate_before"></mwc-icon-button>
       <mwc-slider
         step="1"
         pin
         markers
         min="1"
         max=${ifDefined(this._numConstructionSteps)}
         ?disabled=${this._numConstructionSteps === undefined}
         value=${ifDefined(this.step)}
         @input=${(e: CustomEvent) => this.constructionStep = e.detail.value}
       ></mwc-slider>
       <mwc-icon-button @click=${this._stepForward} icon="navigate_next"></mwc-icon-button>
       <mwc-icon-button @click=${this._resetCamera} icon="center_focus_strong"></mwc-icon-button>
     </div>
   `;
 }
}

İşte son ürün!

Tuğla görüntüleyici öğesiyle araba tuğlası modelinde gezinme

7. Sonuç

Kendi HTML öğemizi oluşturmak için lit-Element'in nasıl kullanılacağı konusunda birçok şey öğrendik. Şu konularda bilgi edindik:

  • Özel öğe tanımlayın
  • Özellik API'si bildirme
  • Özel bir öğe için görünüm oluşturma
  • Stilleri kapsülle
  • Veri aktarmak için etkinlikleri ve özellikleri kullanma

Lit-Element hakkında daha fazla bilgiyi resmi sitesinde bulabilirsiniz.

Tamamlanmış bir tuğla görüntüleyici öğesini stackblitz.com/edit/brick-viewer-complete adresinde görüntüleyebilirsiniz.

tuğla görüntüleyici NPM'de de gönderilir. Kaynağı burada görüntüleyebilirsiniz: GitHub deposu.