สร้าง Brick Viewer ที่มีองค์ประกอบที่สว่าง

1. บทนำ

คอมโพเนนต์เว็บ

คอมโพเนนต์เว็บคือชุดมาตรฐานเว็บที่ช่วยให้นักพัฒนาซอฟต์แวร์ขยาย HTML ด้วยองค์ประกอบที่กําหนดเองได้ ใน Codelab นี้ คุณจะได้กำหนดองค์ประกอบ <brick-viewer> ซึ่งจะแสดงโมเดลตัวต่อได้

lit-element

เราจะใช้องค์ประกอบ "lit" เพื่อช่วยกำหนดองค์ประกอบที่กำหนดเอง <brick-viewer> โดย "lit-element" เป็นคลาสพื้นฐานขนาดเล็กที่เพิ่มน้ำตาลไวยากรณ์ลงในมาตรฐานคอมโพเนนต์เว็บ วิธีนี้จะช่วยให้เราเริ่มต้นใช้งานองค์ประกอบที่กำหนดเองได้ง่าย

เริ่มต้นใช้งาน

เราจะเขียนโค้ดในสภาพแวดล้อม Stackblitz ออนไลน์ ดังนั้นให้เปิดลิงก์นี้ในหน้าต่างใหม่

stackblitz.com/edit/brick-viewer

มาเริ่มกันเลย

2. กำหนดองค์ประกอบที่กำหนดเอง

คำจำกัดความของชั้นเรียน

หากต้องการระบุองค์ประกอบที่กำหนดเอง ให้สร้างคลาสที่ขยาย LitElement และตกแต่งด้วย @customElement อาร์กิวเมนต์ของ @customElement จะเป็นชื่อขององค์ประกอบที่กําหนดเอง

ใน brick-viewer.ts ให้ใส่ข้อมูลต่อไปนี้

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

ตอนนี้องค์ประกอบ <brick-viewer></brick-viewer> พร้อมใช้งานใน HTML แล้ว แต่หากลองใช้ ก็จะไม่แสดงผลใดๆ เรามาแก้ปัญหานั้นกันดีกว่า

วิธีแสดงผล

หากต้องการใช้มุมมองของคอมโพเนนต์ ให้กำหนดเมธอดที่ชื่อว่า Render เมธอดนี้ควรแสดงผลเทมเพลตตัวอักษรล้วนที่ติดแท็กด้วยฟังก์ชัน html ใส่ HTML ที่คุณต้องการในลิเทอรัลของเทมเพลตที่ติดแท็ก ข้อความนี้จะแสดงผลเมื่อคุณใช้ <brick-viewer>

เพิ่มเมธอด render:

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

3. การระบุไฟล์ LDraw

กำหนดพร็อพเพอร์ตี้

คงจะดีไม่น้อยหากผู้ใช้ของ <brick-viewer> สามารถระบุโมเดลตัวต่อที่จะแสดงโดยใช้แอตทริบิวต์ดังตัวอย่างต่อไปนี้

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

เนื่องจากเรากำลังสร้างองค์ประกอบ HTML เราจึงใช้ประโยชน์จาก API แบบประกาศและกำหนดแอตทริบิวต์แหล่งที่มาได้ เช่นเดียวกับแท็ก <img> หรือ <video> เมื่อมีองค์ประกอบส่องสว่างจะช่วยให้ตกแต่งที่พักได้ง่ายดายพอๆ กับการตกแต่งที่พักระดับชั้นเรียนด้วย @property ตัวเลือก type ให้คุณระบุวิธีที่องค์ประกอบที่มีแสงสว่างเพียงพอจะแยกวิเคราะห์พร็อพเพอร์ตี้เพื่อใช้เป็นแอตทริบิวต์ HTML

กำหนดพร็อพเพอร์ตี้และแอตทริบิวต์ src ดังนี้

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

ตอนนี้ <brick-viewer> มีแอตทริบิวต์ src ที่เราตั้งค่าใน HTML ได้ ค่านี้สามารถอ่านได้จากภายในคลาส BrickViewer ด้วยองค์ประกอบที่มีแสง

กำลังแสดงค่า

เราสามารถแสดงค่าของแอตทริบิวต์ src โดยใช้ค่านั้นในเทมเพลตลิเทอรัลของเมธอดแสดงผล แปลงค่าลงในลิเทอรัลเทมเพลตโดยใช้ไวยากรณ์ ${value}

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

ตอนนี้เราเห็นค่าของแอตทริบิวต์ src ในองค์ประกอบ <brick-viewer> ในหน้าต่าง ลองใช้: เปิดเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ของเบราว์เซอร์และเปลี่ยนแอตทริบิวต์ src ด้วยตนเอง ลองใช้งาน...

...คุณสังเกตเห็นไหมว่าข้อความในองค์ประกอบอัปเดตโดยอัตโนมัติ องค์ประกอบที่มีแสงจะคอยสังเกตคุณสมบัติของคลาสที่ตกแต่งด้วย @property แล้วแสดงผลมุมมองให้คุณอีกครั้ง ส่วนองค์ประกอบแสงจะช่วยปลดปล่อยภาระงานให้คุณได้โดยที่คุณไม่ต้องทำอะไรเลย

4. ตั้งค่าฉากด้วย Three.js

แสงพร้อม กล้องพร้อม เรนเดอร์!

องค์ประกอบที่กำหนดเองจะใช้ three.js เพื่อแสดงผลโมเดลอิฐ 3 มิติ เราต้องการทำบางสิ่งเพียงครั้งเดียวสำหรับแต่ละอินสแตนซ์ขององค์ประกอบ <brick-viewer> เช่น ตั้งค่าฉาก 3.js, กล้อง และการจัดแสง เราจะเพิ่มข้อมูลเหล่านี้ลงในคลาส BrickViewer เราจะเก็บวัตถุบางอย่างไว้เป็นคุณสมบัติของคลาสเพื่อใช้ในภายหลัง ได้แก่ กล้อง ฉาก การควบคุม และโหมดแสดงภาพ

เพิ่มการตั้งค่าฉากThree.js ดังนี้

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 มีองค์ประกอบ DOM ที่แสดงฉาก three.js ที่ผ่านการจัดการแสดงผล เข้าถึงได้ผ่านพร็อพเพอร์ตี้ domElement เราสามารถแปลค่านี้ลงในลิเทอรัลของเทมเพลตการแสดงผลโดยใช้ไวยากรณ์ ${value}

นำข้อความ src ที่เรามีในเทมเพลตออก แล้วแทรกองค์ประกอบ DOM ของโปรแกรมแสดงผล

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

หากต้องการให้องค์ประกอบ DOM ของโปรแกรมแสดงผลแสดงอย่างสมบูรณ์ เราจะต้องตั้งค่าองค์ประกอบ <brick-viewer> เป็น display: block ด้วย เราสามารถใส่รูปแบบในพร็อพเพอร์ตี้แบบคงที่ที่เรียกว่า styles ซึ่งตั้งค่าเป็นลิเทอรัลของเทมเพลต css

เพิ่มการจัดรูปแบบนี้ในชั้นเรียน:

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

ตอนนี้ <brick-viewer> กำลังแสดงโหมด 3.js ที่แสดงผล:

องค์ประกอบโปรแกรมดูภาพโมเดลบ้านแสดงฉากที่ผ่านการจัดการแสดงผลแล้วแต่ว่างเปล่า

แต่... ว่างเปล่า เราจะลองระบุโมเดล

รถตักอิฐ

เราจะส่งพร็อพเพอร์ตี้ src ที่เรากำหนดไว้ก่อนหน้านี้ไปยัง LDrawLoader ซึ่งจัดส่งด้วย 3.js

ไฟล์ LDraw สามารถแยกโมเดล Brick เป็นขั้นตอนสิ่งปลูกสร้างที่แยกจากกัน คุณดูจำนวนขั้นตอนทั้งหมดและระดับการมองเห็นของบล็อกแต่ละรายการผ่าน LDrawLoader API ได้

คัดลอกพร็อพเพอร์ตี้เหล่านี้ เมธอด _loadModel ใหม่ และบรรทัดใหม่ในคอนสตรัคเตอร์

@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 เมื่อใด โดยจะต้องเรียกใช้ทุกครั้งที่แอตทริบิวต์ src มีการเปลี่ยนแปลง ในการตกแต่งพร็อพเพอร์ตี้ src ด้วย @property เราได้เลือกใช้วงจรการอัปเดตองค์ประกอบที่มีแสงสว่าง เมื่อใดก็ตามที่ค่าของพร็อพเพอร์ตี้ที่ตกแต่งเหล่านี้เปลี่ยนแปลง ระบบจะเรียกใช้เมธอดชุดหนึ่งที่เข้าถึงค่าใหม่และค่าเก่าของพร็อพเพอร์ตี้ได้ วิธีการใช้วงจรที่เราสนใจชื่อว่า update เมธอด update จะใช้อาร์กิวเมนต์ PropertyValues ซึ่งจะมีข้อมูลเกี่ยวกับพร็อพเพอร์ตี้ที่เพิ่งเปลี่ยนแปลง นี่เป็นวิธีที่เหมาะที่สุดในการโทรหา _loadModel

เพิ่มเมธอด update:

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

ตอนนี้องค์ประกอบ <brick-viewer> สามารถแสดงไฟล์ตัวต่อที่ระบุด้วยแอตทริบิวต์ src ได้แล้ว

องค์ประกอบของคนดูอิฐที่แสดงโมเดลรถยนต์

5. การแสดงโมเดลบางส่วน

ทีนี้เรามากำหนดค่าขั้นตอนการสร้างปัจจุบันกัน เราต้องการระบุ <brick-viewer step="5"></brick-viewer> และควรเห็นลักษณะของโมเดลอิฐในขั้นตอนการก่อสร้างที่ 5 มาทำให้พร็อพเพอร์ตี้ step เป็นพร็อพเพอร์ตี้ที่สังเกตได้ด้วยการตกแต่งด้วย @property

ตกแต่งพร็อพเพอร์ตี้ step ดังนี้

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

ตอนนี้เราจะเพิ่มเมธอดตัวช่วยที่จะแสดงเฉพาะบล็อกจนถึงขั้นตอนการสร้างปัจจุบัน เราจะเรียกใช้ตัวช่วยในเมธอดการอัปเดตเพื่อให้ทำงานทุกครั้งที่พร็อพเพอร์ตี้ step มีการเปลี่ยนแปลง

อัปเดตเมธอด update และเพิ่มเมธอด _updateBricksVisibility ใหม่

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);
  }
}

โอเค ตอนนี้ให้เปิด devtools ของเบราว์เซอร์ แล้วตรวจสอบองค์ประกอบ <brick-viewer> เพิ่มแอตทริบิวต์ step เข้าไปในลักษณะดังนี้

โค้ด HTML ขององค์ประกอบผู้ดูอิฐที่กำหนดแอตทริบิวต์ขั้นตอนเป็น 10

ดูสิ่งที่เกิดขึ้นกับโมเดลที่ผ่านการจัดการแสดงผล เราสามารถใช้แอตทริบิวต์ step เพื่อควบคุมจำนวนของโมเดลที่จะแสดง ลักษณะที่ควรจะเป็นเมื่อตั้งค่าแอตทริบิวต์ step เป็น "10"

โมเดลอิฐที่มีขั้นตอนการต่อเพียง 10 ขั้นตอน

6. การนำทางชุดอิฐ

mwc-icon-button

ผู้ใช้ปลายทางของ <brick-viewer> ควรที่จะสามารถไปยังขั้นตอนบิลด์ผ่าน UI ได้ เรามาเพิ่มปุ่มสำหรับไปที่ขั้นตอนถัดไป ก่อนหน้า และขั้นตอนแรกกัน เราจะใช้องค์ประกอบเว็บของปุ่มของดีไซน์ Material เพื่อทำให้ง่าย เนื่องจากนำเข้า @material/mwc-icon-button แล้ว เราพร้อมที่จะลดลงใน <mwc-icon-button></mwc-icon-button> เราระบุไอคอนที่ต้องการใช้กับแอตทริบิวต์ไอคอนได้ เช่น <mwc-icon-button icon="thumb_up"></mwc-icon-button> ดูไอคอนทั้งหมดที่เป็นไปได้ได้ที่นี่ material.io/resources/icons

ลองเพิ่มปุ่มไอคอนบางปุ่มลงในวิธีการแสดงผลกัน

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>
    `;
  }
}

การใช้ดีไซน์ Material ในหน้าเว็บของเรานั้นง่ายนิดเดียว ด้วยองค์ประกอบของเว็บ

การเชื่อมโยงเหตุการณ์

ปุ่มเหล่านี้ควรทํางานจริง ปุ่ม "ตอบกลับ" ควรรีเซ็ตขั้นตอนการสร้างเป็น 1 ปุ่ม "navigate_before" ควรลดขั้นตอนการสร้าง และปุ่ม "navigate_next" ควรเพิ่มขึ้น องค์ประกอบที่มีแสงสว่างช่วยให้เพิ่มฟังก์ชันการทำงานนี้ได้ง่ายด้วยการเชื่อมโยงเหตุการณ์ ในลิเทอรัลเทมเพลต HTML ให้ใช้ไวยากรณ์ @eventname=${eventHandler} เป็นแอตทริบิวต์ขององค์ประกอบ ตอนนี้ eventHandler จะทํางานเมื่อตรวจพบเหตุการณ์ eventname ในองค์ประกอบนั้น ลองดูตัวอย่างจากการเพิ่มเครื่องจัดการกิจกรรมการคลิกลงในปุ่มไอคอน 3 ปุ่มดังนี้

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>
    `;
  }
}

ลองคลิกปุ่มต่างๆ ได้ทันที เยี่ยมมาก

รูปแบบ

ปุ่มใช้งานได้ แต่ประสิทธิภาพไม่ดี ทุกคนอยู่รวมกันที่ด้านล่าง มาจัดรูปแบบให้วางซ้อนกันในฉากกัน

หากต้องการใช้สไตล์กับปุ่มเหล่านี้ เราจะกลับไปที่พร็อพเพอร์ตี้ static styles รูปแบบเหล่านี้มีขอบเขต ซึ่งหมายความว่าจะมีผลกับองค์ประกอบภายในคอมโพเนนต์เว็บนี้เท่านั้น ซึ่งนี่เป็นหนึ่งในข้อดีของการเขียนคอมโพเนนต์เว็บ ตัวเลือกจะเขียนได้ง่ายขึ้น และ CSS จะอ่านและเขียนได้ง่ายขึ้น สวัสดี BEM

อัปเดตรูปแบบให้มีลักษณะเช่นนี้

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

องค์ประกอบของผู้ดูอิฐที่มีปุ่มรีสตาร์ท ย้อนกลับ และไปข้างหน้า

รีเซ็ตปุ่มกล้อง

ผู้ใช้ปลายทางของ <brick-viewer> สามารถหมุนฉากโดยใช้การควบคุมด้วยเมาส์ ระหว่างที่เราเพิ่มปุ่ม มาเพิ่มปุ่มสำหรับรีเซ็ตกล้องไปยังตำแหน่งเริ่มต้นกัน <mwc-icon-button> อีกรายการหนึ่งที่มีการเชื่อมโยงเหตุการณ์คลิกจะทํางานได้

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>
    `;
  }
}

การนำทางที่รวดเร็วขึ้น

ชุดบล็อกบางชุดมีขั้นตอนจำนวนมาก ผู้ใช้อาจต้องการข้ามไปยังขั้นตอนที่เฉพาะเจาะจง การเพิ่มแถบเลื่อนพร้อมหมายเลขขั้นตอนจะช่วยในการไปยังส่วนต่างๆ ได้อย่างรวดเร็ว เราจะใช้องค์ประกอบ <mwc-slider> สำหรับขั้นตอนนี้

แถบเลื่อน mwc

องค์ประกอบแถบเลื่อนต้องมีข้อมูลสำคัญ 2-3 อย่าง เช่น ค่าต่ำสุดและสูงสุดของแถบเลื่อน ค่าต่ำสุดของแถบเลื่อนต้องเป็น "1" เสมอ ค่าสูงสุดของแถบเลื่อนควรเป็น this._numConstructionSteps หากโหลดโมเดลแล้ว เราบอกค่าเหล่านี้ได้ <mwc-slider> ผ่านแอตทริบิวต์ นอกจากนี้ เรายังใช้คําสั่ง ifDefined lit-html เพื่อหลีกเลี่ยงการตั้งค่าแอตทริบิวต์ max ได้หากไม่ได้กําหนดพร็อพเพอร์ตี้ _numConstructionSteps

เพิ่ม <mwc-slider> ระหว่างปุ่ม "ย้อนกลับ" และ "ไปข้างหน้า"

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>
    `;
  }
}

ข้อมูล "ขึ้น"

เมื่อผู้ใช้เลื่อนแถบเลื่อน ขั้นตอนการสร้างในปัจจุบันควรเปลี่ยนแปลงไป และการแสดงผลของโมเดลควรได้รับการอัปเดตให้สอดคล้องกัน องค์ประกอบแถบเลื่อนจะส่งเหตุการณ์อินพุตทุกครั้งที่มีการลากแถบเลื่อน เพิ่มการเชื่อมโยงเหตุการณ์ในแถบเลื่อนเองเพื่อจับเหตุการณ์นี้และเปลี่ยนขั้นตอนการสร้าง

เพิ่มการเชื่อมโยงกิจกรรม

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>
    `;
  }
}

เย่! เราสามารถใช้แถบเลื่อนเพื่อเปลี่ยนขั้นตอนที่จะแสดงได้

ข้อมูล "ลดลง"

อีกเรื่องหนึ่ง เมื่อใช้ปุ่ม "ย้อนกลับ" และ "ถัดไป" เพื่อเปลี่ยนขั้นตอน ต้องอัปเดตแฮนเดิลแถบเลื่อน เชื่อมโยงแอตทริบิวต์ค่าของ <mwc-slider> กับ this.step

เพิ่มการเชื่อมโยง value:

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>
    `;
  }
}

เรากําลังจะสร้างแถบเลื่อนเกือบเสร็จแล้ว เพิ่มสไตล์ Flex เพื่อให้ทำงานร่วมกับตัวควบคุมอื่นๆ ได้ดี

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

นอกจากนี้ เรายังต้องเรียก layout ในองค์ประกอบแถบเลื่อนด้วย เราจะทำแบบนั้นในเมธอดอายุการใช้งาน firstUpdated ซึ่งเรียกว่าเมื่อมีการจัดวาง DOM ครั้งแรก เครื่องมือตกแต่ง query จะช่วยให้เรารับการอ้างอิงองค์ประกอบแถบเลื่อนในเทมเพลตได้

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

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

รายการต่อไปนี้คือการเพิ่มแถบเลื่อนทั้งหมดไว้ด้วยกัน (พร้อมแอตทริบิวต์ pin และ markers เพิ่มเติมบนแถบเลื่อนเพื่อทำให้ดูเท่)

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>
   `;
 }
}

สินค้าสุดท้ายมาแล้ว

การไปยังส่วนต่างๆ ของโมเดลบล็อกรถยนต์ด้วยองค์ประกอบ brick-viewer

7. บทสรุป

เราเรียนรู้วิธีใช้ lit-element เพื่อสร้างองค์ประกอบ HTML ของเราเองมากมาย เราเรียนรู้วิธีต่อไปนี้

  • กำหนดองค์ประกอบที่กำหนดเอง
  • ประกาศแอตทริบิวต์ API
  • แสดงผลมุมมองสําหรับองค์ประกอบที่กําหนดเอง
  • บรรจุรูปแบบ
  • ใช้เหตุการณ์และพร็อพเพอร์ตี้เพื่อส่งข้อมูล

หากต้องการดูข้อมูลเพิ่มเติมเกี่ยวกับ lit-element โปรดอ่านเว็บไซต์อย่างเป็นทางการ

คุณดูองค์ประกอบ brick-viewer ที่เสร็จสมบูรณ์ได้ที่ stackblitz.com/edit/brick-viewer-complete

นอกจากนี้ brick-viewer ยังมาพร้อมกับ NPM และคุณสามารถดูแหล่งที่มาได้ที่นี่ Github repo