1. Giriş
Web bileşenleri
Web bileşenleri, geliştiricilerin HTML'yi özel öğelerle genişletmesine olanak tanıyan bir web standartları koleksiyonudur. Bu codelab'de, tuğla modellerini görüntüleyebilecek <brick-viewer> öğesini tanımlayacaksınız.
lit-element
Özel öğemizi <brick-viewer> tanımlamamıza yardımcı olması için lit-element'i kullanacağız. lit-element, web bileşenleri standardına biraz söz dizimi kolaylığı ekleyen hafif bir temel sınıftır. Bu sayede özel öğemizi kolayca kullanmaya başlayabiliriz.
Başlayın
Online Stackblitz ortamında kodlama yapacağı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ımlama
Sınıf tanımı
Özel bir öğe tanımlamak için LitElement öğesini genişleten bir sınıf oluşturun ve bu sınıfı @customElement ile süsleyin. @customElement işlevinin bağımsız değişkeni, özel öğenin adı olur.
brick-viewer.ts dosyasında şunları girin:
@customElement('brick-viewer')
export class BrickViewer extends LitElement {
}
Artık <brick-viewer></brick-viewer> öğesi HTML'de kullanıma hazır. Ancak bunu denerseniz hiçbir şey oluşturulmaz. Gelin bu sorunu çözelim.
Oluşturma yöntemi
Bileşenin görünümünü uygulamak için render adlı bir yöntem tanımlayın. Bu yöntem, html işleviyle etiketlenmiş bir şablon değişmez değeri döndürmelidir. Etiketlenmiş şablon değişmezine istediğiniz HTML'yi yerleştirin. Bu, <brick-viewer> özelliğini 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
Mülk tanımlama
<brick-viewer> kullanıcısının, hangi tuğla modelinin gösterileceğini aşağıdaki gibi bir özellik kullanarak belirtmesi iyi olur:
<brick-viewer src="path/to/model.ldraw"></brick-viewer>
Bir HTML öğesi oluşturduğumuz için bildirimsel API'den yararlanabilir ve <img> veya <video> etiketi gibi bir kaynak özelliği tanımlayabiliriz. lit-element ile bu işlem, sınıf özelliğini @property ile süslemek kadar kolaydır. type seçeneği, lit-element'in özelliği HTML özelliği olarak kullanılmak üzere nasıl ayrıştıracağını belirtmenize olanak tanır.
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 okunabilir.
Değerleri görüntüleme
Oluşturma yönteminin şablon değişmezinde kullanarak src özelliğinin değerini gösterebiliriz. ${value} söz dizimini kullanarak değerleri şablon değişmezlerine yerleştirin.
export class BrickViewer extends LitElement {
render() {
return html`<div>Brick model: ${this.src}</div>`;
}
}
Şimdi pencerede <brick-viewer> öğesindeki src özelliğinin değerini 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 süslenmiş sınıf özelliklerini gözlemler ve görünümü sizin için yeniden oluşturur. Ağır işleri lit-element yapar, böylece siz yapmazsınız.
4. Three.js ile Sahneyi Ayarlama
Işık, kamera, oluşturma!
Özel öğemiz, 3D tuğla modellerimizi oluşturmak için three.js'yi kullanacak. <brick-viewer> öğesinin her örneği için yalnızca bir kez yapmak istediğimiz bazı şeyler vardır. Örneğin, three.js sahnesini, kamerayı ve aydınlatmayı ayarlamak. Bunları BrickViewer sınıfının oluşturucusuna ekleyeceğiz. Bazı nesneleri daha sonra kullanabilmek için sınıf özellikleri olarak tutacağız: kamera, sahne, kontroller ve oluşturucu.
three.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şturulan three.js sahnesini görüntüleyen 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 değişmezine yerleştirebiliriz.
Ş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 tamamen gösterilmesine izin vermek için <brick-viewer> öğesinin kendisini de display: block olarak ayarlamamız gerekir. styles adlı statik bir özellikte, css şablon değişmezine ayarlanmış stiller 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;
}
`;
}
Şimdi <brick-viewer>, oluşturulmuş bir three.js sahnesi gösteriyor:

Ancak... boş. Bir modelle besleyelim.
Tuğla yükleyici
Daha önce tanımladığımız src özelliğini, three.js ile birlikte gelen LDrawLoader'a ileteceğiz.
LDraw dosyaları, bir Lego modelini ayrı yapı adımlarına ayırabilir. Toplam adım sayısı ve her bir parçanın görünürlüğü 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 aranmalıdır? src özelliği her değiştiğinde çağrılması gerekir. src özelliğini @property ile süsleyerek özelliği lit-element güncelleme yaşam döngüsüne dahil etmiş olduk. Bu süslenmiş özelliklerden birinin değeri her değiştiğinde, ö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'dır. update yöntemi, yeni değişen özelliklerle ilgili bilgileri içeren bir PropertyValues bağımsız değişkeni alır. Bu, _loadModel numaralı telefonu aramak için mükemmel bir yerdir.
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österebilir.

5. Kısmi Modelleri Görüntüleme
Şimdi de mevcut yapılandırma adımını yapılandırılabilir hale getirelim. <brick-viewer step="5"></brick-viewer> değerini belirleyebilmek istiyoruz ve 5. inşaat adımında tuğla modelinin nasıl göründüğünü görmemiz gerekiyor. Bunu yapmak için step özelliğini @property ile süsleyerek gözlemlenen bir özellik haline getirelim.
step özelliğine parametre ekleyin:
export class BrickViewer extends LitElement {
@property({type: Number})
step?: number;
}
Şimdi, yalnızca mevcut derleme adımına kadar olan tuğlaları görünür kılan bir yardımcı yöntem ekleyeceğiz. step özelliği her değiştirildiğinde çalışması için yardımcı işlevi güncelleme yönteminde çağıracağı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);
}
}
Şimdi tarayıcınızın geliştirme araçlarını açın ve <brick-viewer> öğesini inceleyin. Aşağıdaki gibi bir step özelliği ekleyin:

Oluşturulan modelde neler 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 görünüm aşağıdaki gibi olmalıdır:

6. Brick Set Navigation
mwc-icon-button
<brick-viewer> ürünümüzün son kullanıcısı, derleme adımlarında kullanıcı arayüzü üzerinden de gezinebilmelidir. Sonraki adıma, önceki adıma ve ilk adıma gitmek için düğmeler ekleyelim. İşlemi kolaylaştırmak için Material Design'ın düğme web bileşenini kullanacağız. @material/mwc-icon-button zaten içe aktarıldığından <mwc-icon-button></mwc-icon-button> öğesini eklemeye hazırız. Kullanmak istediğimiz simgeyi, simge özelliğiyle şu şekilde belirtebiliriz: <mwc-icon-button icon="thumb_up"></mwc-icon-button>. Olası tüm simgeleri material.io/resources/icons adresinde bulabilirsiniz.
Oluşturma yöntemine bazı simge düğmeleri 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>
`;
}
}
Web bileşenleri sayesinde sayfamızda Materyal Tasarımı kullanmak bu kadar kolay.
Etkinlik bağlamaları
Bu düğmelerin gerçekten bir işe yaraması gerekir. “Yanıtla” düğmesi, oluşturma adımını 1'e sıfırlamalıdır. "navigate_before" düğmesi, yapım adımını azaltmalı, "navigate_next" düğmesi ise artırmalıdır. lit-element, etkinlik bağlamalarıyla bu işlevi eklemeyi kolaylaştırır. HTML şablonunuzda @eventname=${eventHandler} söz dizimini öğe özelliği olarak kullanın. eventHandler, artık söz konusu öğede bir eventname etkinliği algılandığında çalışacak. Örnek olarak, üç simge düğmemize tıklama etkinlik 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üyor. Hepsi en altta toplanmış. Şimdi bunları sahnenin üzerine yerleştirecek şekilde biçimlendirelim.
Bu düğmelere stil uygulamak için static styles özelliğine geri dönüyoruz. Bu stiller kapsamlıdır. Yani yalnızca bu web bileşenindeki öğeler için geçerli olurlar. Bu, web bileşenleri yazmanın avantajlarından biridir: Seçiciler daha basit olabilir ve CSS'nin okunması ve yazılması daha kolay olur. Hoşça kal BEM!
Stilleri aşağıdaki gibi 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;
}
`;
}

Kamerayı sıfırla düğmesi
<brick-viewer> ürünümüzün son kullanıcıları, fare kontrollerini kullanarak sahneyi döndürebilir. Düğme eklerken kamerayı varsayılan konumuna sıfırlamak için de bir düğme ekleyelim. Tıklama etkinliği bağlaması olan başka bir <mwc-icon-button> ile işi halledebilirsiniz.
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ı LEGO setleri çok fazla adım içerir. Kullanıcılar belirli bir adıma geçmek 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-slider
Kaydırma çubuğu öğesi için minimum ve maksimum kaydırma çubuğu değeri gibi önemli bazı veriler gerekir. Minimum kaydırma çubuğu değeri her zaman "1" olabilir. Model yüklendiyse kaydırma çubuğunun maksimum değeri this._numConstructionSteps olmalıdır. Bu değerleri özelliklerinden <mwc-slider> anlayabiliriz. ifDefined özelliğinin tanımlanmadığı durumlarda max özelliğini ayarlamamak için ifDefined lit-html yönergesini de kullanabiliriz._numConstructionSteps
"Geri" ve "ileri" düğmelerinin arasına <mwc-slider> ekleyin:
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ı"
Kullanıcı kaydırma çubuğunu hareket ettirdiğinde mevcut yapı adımı değişmeli ve modelin görünürlüğü buna göre güncellenmelidir. Kaydırma çubuğu öğesi, kaydırma çubuğu her sürüklendiğinde bir giriş etkinliği yayar. Bu etkinliği yakalamak ve yapım adımını değiştirmek için kaydırma çubuğuna etkinlik bağlama ekleyin.
Etkinlik bağlamasını 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.
Veriler "kapalı"
Bir şey daha var. Adımı değiştirmek için "geri" ve "ileri" düğmeleri kullanıldığında kaydırma çubuğu tutamacının güncellenmesi gerekir. <mwc-slider>'nın değer özelliğini this.step'ye 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ğuyla ilgili işlemler neredeyse tamamlandı. Diğer kontrollerle uyumlu olması için esnek stil ekleyin:
export class BrickViewer extends LitElement {
static styles = css`
/* ... */
mwc-slider {
flex-grow: 1;
}
`;
}
Ayrıca, kaydırma çubuğu öğesinde layout işlevini çağırmamız gerekir. Bunu, DOM ilk kez yerleştirildikten sonra bir kez çağrılan firstUpdated yaşam döngüsü yönteminde yapacağız. query dekoratörü, şablondaki kaydırma çubuğu öğesine 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();
}
}
}
Kaydırıcıya eklenen tüm öğeler (kaydırıcının havalı görünmesi için ek pin ve markers özellikleriyle birlikte):
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 nihai ürün!

7. Sonuç
Kendi HTML öğemizi oluşturmak için lit-element'i nasıl kullanacağımız hakkında çok şey öğrendik. Öğrendiklerimiz:
- Özel öğe tanımlama
- Özellik API'si bildirme
- Özel öğe için görünüm oluşturma
- Stilleri kapsülleme
- Veri iletmek için etkinlikleri ve özellikleri kullanma
lit-element hakkında daha fazla bilgi edinmek isterseniz resmi sitesini ziyaret edebilirsiniz.
Tamamlanmış bir brick-viewer öğesini stackblitz.com/edit/brick-viewer-complete adresinde görüntüleyebilirsiniz.
brick-viewer, NPM'de de gönderilir ve kaynağı buradan görüntüleyebilirsiniz: Github deposu.