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 modellerini görüntüleyebilecek <brick-viewer>
öğesini tanımlayacaksınız.
ışık-öğesi
Özel öğemizi <brick-viewer>
tanımlamamıza yardımcı olması için lit-element'i kullanacağız. lit-element, web bileşenleri standardına bazı söz dizimi süslemeleri 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 bir Stackblitz ortamında kod yazacağız. Bu nedenle, bu 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 bir öğe tanımlamak için LitElement
sınıfını genişleten bir sınıf oluşturun ve bu sınıfı @customElement
ile süsleyin. @customElement
bağımsız değişkeni, özel öğenin adıdı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 şablon literali döndürmelidir. Etiketli şablon literaline istediğiniz HTML'yi koyun. 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
Mülk tanımlama
<brick-viewer>
kullanıcılarının, aşağıdaki gibi bir özellik kullanarak hangi tuğla modelinin gösterileceğini belirtebilmesi çok iyi olurdu:
<brick-viewer src="path/to/model.ldraw"></brick-viewer>
Bir HTML öğesi oluşturduğumuzdan, açıklayıcı API'den yararlanabilir ve <img>
veya <video>
etiketi gibi bir kaynak özelliği tanımlayabiliriz. lit-element ile bu işlem, bir 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 kullanmak için nasıl ayrıştırdığı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
src
özelliğinin değerini, oluşturma yönteminin şablon literalinde kullanarak gösterebiliriz. ${value}
söz dizimini kullanarak değerleri şablon literallerine ekleyin.
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? Işık öğesi, @property
ile dekore edilmiş sınıf özelliklerini gözlemler ve görünümü sizin için yeniden oluşturur. Işık öğesi işin zor kısmını üstlenerek sizin yapmanıza gerek kalmaz.
4. Three.js ile sahneyi hazırlayın
Işık, Kamera, Oluştur!
Ö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ının kurucusuna 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:
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 kurucudaki 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 süsleyerek mülkü lit-element güncelleme yaşam döngüsüne dahil ettik. Bu dekore edilmiş özelliklerin değeri 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öntemi update
olarak adlandırılır. update
yöntemi, yeni değişen tüm mülkler 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.
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 belirtmek istiyoruz ve 5. yapı adımında tuğla modelinin nasıl göründüğünü görmemiz gerekiyor. Bunun için step
özelliğini @property
ile süsleyerek gözlemlenen bir mülk yapalım.
step
mülkünü süsleyin:
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. step
mülkü her değiştiğinde çalışacak şekilde yardımcıyı 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);
}
}
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:
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 aşağıdaki gibi görünmelidir:
6. Tuğla Sette Gezinme
mwc-icon-button
<brick-viewer>
uygulamamızın son kullanıcısı da kullanıcı arayüzü üzerinden derleme adımlarında gezinebilir. Sonraki adıma, önceki adıma ve ilk adıma gitmek için düğmeler ekleyelim. Bu işlemi 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ığı için <mwc-icon-button></mwc-icon-button>
'e eklemeye hazırız. Kullanmak istediğimiz simgeyi icon özelliğiyle belirtebiliriz. Örneğin: <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ğlamaları
Bu düğmeler gerçekten bir işlev görmelidir. "Yanıtla" düğmesi, inşaat adımını 1'e sıfırlamalıdır. "Gezinme_before" düğmesi, oluşturma adımını azaltmalı ve "navigation_next" düğmesi bunu artırmalıdır. lit-element, etkinlik bağlamalarıyla bu işlevin eklenmesini kolaylaştırır. HTML şablonunuzda öğe özelliği olarak @eventname=${eventHandler}
söz dizimini kullanın. eventHandler
, artık ilgili öğ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 toplanmış. Bunları sahneye yerleştirecek şekilde biçimlendirelim.
Bu düğmelere stil uygulamak için static styles
özelliğine geri döneriz. Bu stiller kapsamlıdır. Yani yalnızca bu web bileşenindeki öğelere uygulanır. 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;
}
`;
}
Kamerayı sıfırla düğmesi
<brick-viewer>
'ü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 tamamlar.
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ılar 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. Kaydırma çubuğunun minimum değeri her zaman "1" olabilir. Model yüklenmişse maksimum kaydırma çubuğu değeri this._numConstructionSteps
olmalıdır. <mwc-slider>
'ye bu değerleri özelliklerini kullanarak bildirebiliriz. _numConstructionSteps
özelliği tanımlanmamışsa max
özelliğini ayarlamaktan kaçınmak için ifDefined
lit-html directive de kullanabiliriz.
"Geri" ve "ileri" düğmeleri 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>
`;
}
}
Veriler "yukarı"
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 öğesi, kaydırma çubuğu her sürüklendiğinde 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. Adımı değiştirmek için "geri" ve "sonraki" düğmeleri kullanıldığında 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
çağrılmalıdır. 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
süsleyici, ş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ırma çubuğunun tüm eklemeleri birlikte gösterilmiştir (kaydırma çubuğunun şık görünmesi için ek pin
ve markers
özellikleri eklenmiş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>
`;
}
}
Nihai ürüne göz atın.
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ımlama
- Ö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 brick-viewer öğesini stackblitz.com/edit/brick-viewer-complete adresinde görüntüleyebilirsiniz.
brick-viewer, NPM'de de gönderilir. Kaynağı şu adreste görüntüleyebilirsiniz: GitHub repo.