สร้างคอมโพเนนต์เรื่องราวที่มีองค์ประกอบที่สว่าง

1. บทนำ

เรื่องราวเป็นองค์ประกอบ UI ที่ได้รับความนิยมในปัจจุบัน แอปโซเชียลและแอปข่าวจะรวมแอปดังกล่าวลงในฟีด ใน Codelab นี้ เราจะสร้างคอมโพเนนต์เรื่องราวด้วย lit-element และ TypeScript

นี่คือลักษณะของคอมโพเนนต์เรื่องราวในตอนท้าย

คอมโพเนนต์ของโปรแกรมดูเรื่องราวที่สมบูรณ์ซึ่งแสดงรูปภาพกาแฟ 3 ภาพ

เรานึกถึง "เรื่องราว" บนโซเชียลมีเดียหรือข่าว เป็นคอลเล็กชันการ์ดที่จะเล่นตามลำดับ คล้ายๆ กับสไลด์โชว์ ที่จริงแล้วเรื่องราวก็คือภาพสไลด์ การ์ดมักเป็นรูปภาพหรือวิดีโอที่เล่นอัตโนมัติ และใส่ข้อความเพิ่มเติมด้านบนได้ นี่คือสิ่งที่เราจะสร้าง

รายการฟีเจอร์

  • การ์ดที่มีรูปภาพหรือพื้นหลังวิดีโอ
  • ปัดไปทางซ้ายหรือขวาเพื่อไปยังส่วนต่างๆ ของเรื่องราว
  • การเล่นวิดีโออัตโนมัติ
  • สามารถเพิ่มข้อความหรือปรับแต่งการ์ด

สำหรับประสบการณ์ของนักพัฒนาซอฟต์แวร์คอมโพเนนต์นี้ คุณควรระบุการ์ดเรื่องราวในมาร์กอัป HTML ธรรมดาดังนี้

<story-viewer>
  <story-card>
    <img slot="media" src="some/image.jpg" />
    <h1>Title</h1>
  </story-card>
  <story-card>
    <video slot="media" src="some/video.mp4" loop playsinline></video>
    <h1>Whatever</h1>
    <p>I want!</p>
  </story-card>
</story-viewer>

งั้นเรามาเพิ่ม ฟีเจอร์นั้นในรายการฟีเจอร์กัน

รายการฟีเจอร์

  • ยอมรับชุดการ์ดในมาร์กอัป HTML

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

ข้อกำหนดเบื้องต้น

  • Shell ที่คุณเรียกใช้ git และ npm ได้
  • เครื่องมือแก้ไขข้อความ

2. การตั้งค่า

เริ่มต้นด้วยการโคลนที่เก็บนี้ story-viewer-starter

git clone git@github.com:PolymerLabs/story-viewer-starter.git

มีการตั้งค่าสภาพแวดล้อมด้วยองค์ประกอบที่มีแสงน้อยและ TypeScript อยู่แล้ว เพียงติดตั้งทรัพยากร Dependency ต่อไปนี้

npm i

สำหรับผู้ใช้ VS Code ให้ติดตั้งส่วนขยาย lit-plugin เพื่อรับการเติมข้อความอัตโนมัติ การตรวจสอบประเภท และการกำจัดไฟล์ของเทมเพลต lit-html

เริ่มต้นสภาพแวดล้อมในการพัฒนาซอฟต์แวร์โดยการเรียกใช้:

npm run dev

คุณพร้อมที่จะเริ่มเขียนโค้ดแล้ว

3. <การ์ดเรื่องราว> ส่วนประกอบ

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

ขั้นตอนแรกคือการกำหนดคลาสของคอมโพเนนต์ซึ่งจะขยาย LitElement นักตกแต่ง customElement จะดูแลการลงทะเบียนองค์ประกอบที่กำหนดเองให้เรา ตอนนี้เป็นโอกาสที่ดีในการตรวจสอบว่าได้เปิดใช้เครื่องมือตกแต่งใน tsconfig ด้วยแฟล็ก experimentalDecorators (หากคุณใช้ที่เก็บเริ่มต้น ระบบจะเปิดใช้ฟีเจอร์นี้อยู่แล้ว)

ใส่โค้ดต่อไปนี้ลงใน Story-card.ts:

import { LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('story-card')
export class StoryCard extends LitElement {
}

ตอนนี้ <story-card> เป็นองค์ประกอบที่กำหนดเองที่ใช้ได้แล้ว แต่ยังไม่ได้แสดงอะไรเลย หากต้องการกำหนดโครงสร้างภายในขององค์ประกอบ ให้กำหนดเมธอดอินสแตนซ์ render นี่คือที่ที่เราจะให้เทมเพลตสำหรับองค์ประกอบ โดยใช้แท็ก html ของ lit-html

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

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

import { html } from 'lit';

export class StoryCard extends LitElement {
  render() {
    return html`
      <div id="media">
        <slot name="media"></slot>
      </div>
      <div id="content">
        <slot></slot>
      </div>
    `;
  }
}

การแยกองค์ประกอบสื่อให้เป็นช่องของตัวเองจะช่วยให้เรากำหนดเป้าหมายองค์ประกอบนั้นสำหรับสิ่งต่างๆ เช่น การเพิ่มสไตล์แบบไม่มีขอบและวิดีโอที่เล่นอัตโนมัติ ใส่ช่องที่ 2 (ช่องสำหรับการวางซ้อนที่กำหนดเอง) ลงในเอลิเมนต์คอนเทนเนอร์เพื่อให้เราเพิ่มระยะห่างจากขอบเริ่มต้นบางส่วนได้ในภายหลัง

คอมโพเนนต์ <story-card> จะใช้ลักษณะต่อไปนี้ได้

<story-card>
  <img slot="media" src="some/image.jpg" />
  <h1>My Title</h1>
  <p>my description</p>
</story-card>

แต่ดูแย่มาก

นักเล่าเรื่องที่ไร้สไตล์ที่แสดงภาพกาแฟ

กำลังเพิ่มสไตล์

มาเพิ่มสไตล์กัน ซึ่งก็ใช้องค์ประกอบที่มีแสงน้อยได้โดยกำหนดพร็อพเพอร์ตี้ styles แบบคงที่และส่งสตริงเทมเพลตที่แท็ก css กลับมา CSS ที่เขียนไว้ที่นี่จะมีผลกับองค์ประกอบที่กำหนดเองของเราเท่านั้น CSS ที่มี Shadow DOM ทำงานได้ดี

มาจัดรูปแบบองค์ประกอบสื่อแบบสล็อตแมชชีนให้ครอบคลุม <story-card> กัน ไหนๆ ก็มาถึงจุดนี้ เราสามารถให้การจัดรูปแบบที่ดีสำหรับองค์ประกอบในช่องที่ 2 ได้ วิธีนี้จะช่วยให้ผู้ใช้คอมโพเนนต์สามารถข้าม <h1>, <p> หรืออะไรก็ได้ และจะเห็นข้อความที่น่าสนใจโดยค่าเริ่มต้น

import { css } from 'lit';

export class StoryCard extends LitElement {
  static styles = css`
    #media {
      height: 100%;
    }
    #media ::slotted(*) {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    /* Default styles for content */
    #content {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      padding: 48px;
      font-family: sans-serif;
      color: white;
      font-size: 24px;
    }
    #content > slot::slotted(*) {
      margin: 0;
    }
  `;
}

นักเล่าเรื่องที่มีสไตล์แสดงภาพกาแฟ

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

4. < Stories-viewer> ส่วนประกอบ

องค์ประกอบ <story-viewer> ของเราเป็นพาเรนต์ของ <story-card> โมเดลนี้มีหน้าที่จัดวางการ์ดในแนวนอนและให้เราปัดการ์ดไปมา เราจะเริ่มจากวิธีเดียวกับตอนที่ StoryCard เราต้องการเพิ่มการ์ดเรื่องราวเป็นองค์ประกอบย่อยขององค์ประกอบ <story-viewer> ดังนั้นให้เพิ่มช่องสำหรับเด็กๆ เหล่านั้น

ใส่โค้ดต่อไปนี้ใน Story-viewer.ts:

import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('story-viewer')
export class StoryViewer extends LitElement {
  render() {
    return html`<slot></slot>`;
  }
}

ต่อไปเป็นเลย์เอาต์แนวนอน ซึ่งเราสามารถทำเช่นนี้ได้โดยกำหนดตำแหน่งสัมบูรณ์ของ <story-card> แบบสล็อตแมชชีนทั้งหมด และแปลตำแหน่งเหล่านั้นตามดัชนี เรากำหนดเป้าหมายตัวองค์ประกอบ <story-viewer> เองได้โดยใช้ตัวเลือก :host

static styles = css`
  :host {
    display: block;
    position: relative;
    /* Default size */
    width: 300px;
    height: 800px;
  }
  ::slotted(*) {
    position: absolute;
    width: 100%;
    height: 100%;
  }`;

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

story-viewer {
  width: 400px;
  max-width: 100%;
  height: 80%;
}

หากต้องการติดตามการ์ดที่ดูอยู่ในปัจจุบัน มาเพิ่มตัวแปรอินสแตนซ์ index ลงในคลาส StoryViewer การตกแต่งองค์ประกอบด้วย @property ของ LitElement จะทำให้คอมโพเนนต์แสดงผลอีกครั้งเมื่อค่ามีการเปลี่ยนแปลง

import { property } from 'lit/decorators.js';

export class StoryViewer extends LitElement {
  @property({type: Number}) index: number = 0;
}

ต้องแปลการ์ดแต่ละใบในแนวนอน มาใช้คําแปลเหล่านี้ในวิธีอายุการใช้งาน update ขององค์ประกอบที่มีแสงจ้ากัน วิธีการอัปเดตจะทำงานทุกครั้งที่คุณสมบัติที่สังเกตได้ของคอมโพเนนต์มีการเปลี่ยนแปลง โดยปกติแล้ว เราจะค้นหาช่องและวนซ้ำ slot.assignedElements() อย่างไรก็ตาม เนื่องจากเรามีช่องที่ไม่มีชื่อเพียงช่องเดียวเท่านั้น จึงเหมือนกับการใช้ this.children จะใช้ this.children เพื่อความสะดวกนะ

import { PropertyValues } from 'lit';

export class StoryViewer extends LitElement {
  update(changedProperties: PropertyValues) {
    const width = this.clientWidth;
    Array.from(this.children).forEach((el: Element, i) => {
      const x = (i - this.index) * width;
      (el as HTMLElement).style.transform = `translate3d(${x}px,0,0)`;
    });
    super.update(changedProperties);
  }
}

<story-card> ทั้งหมดติดกันแล้ว องค์ประกอบนี้ยังคงทำงานร่วมกับองค์ประกอบอื่นๆ ได้ตั้งแต่เด็ก ตราบใดที่เราออกแบบองค์ประกอบเหล่านั้นอย่างเหมาะสม ดังนี้

<story-viewer>
  <!-- A regular story-card child... -->
  <story-card>
    <video slot="media" src="some/video.mp4"></video>
    <h1>This video</h1>
    <p>is so cool.</p>
  </story-card>
  <!-- ...and other elements work too! -->
  <img style="object-fit: cover" src="some/img.png" />
</story-viewer>

ไปที่ build/index.html และ uncomment องค์ประกอบการ์ดเรื่องราวที่เหลือ ทีนี้ก็มาเริ่มกันเลย เราจะได้นำทางพวกเขาไป

5. แถบความคืบหน้าและการนำทาง

ถัดไป เราจะเพิ่มวิธีไปยังการ์ดต่างๆ และแถบความคืบหน้า

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

ใน Story-viewer.ts ให้เพิ่มสิ่งต่อไปนี้ในชั้นเรียน StoryViewer

/** Advance to the next story card if possible **/
next() {
  this.index = Math.max(0, Math.min(this.children.length - 1, this.index + 1));
}

/** Go back to the previous story card if possible **/
previous() {
  this.index = Math.max(0, Math.min(this.children.length - 1, this.index - 1));
}

ในการแสดงการนำทางแก่ผู้ใช้ปลายทาง เราจะเพิ่ม "ก่อนหน้า" และ "ถัดไป" กับ <story-viewer> เมื่อคลิกปุ่มใดปุ่มหนึ่ง เราจะเรียกใช้ฟังก์ชันตัวช่วยของ next หรือ previous lit-html ช่วยให้เพิ่ม Listener เหตุการณ์ลงในองค์ประกอบได้ง่ายขึ้น เราจะแสดงผลปุ่มและเพิ่ม Listener การคลิกได้พร้อมกัน

อัปเดตเมธอด render เป็นเมธอดต่อไปนี้

export class StoryViewer extends LitElement {
  render() {
    return html`
      <slot></slot>

      <svg id="prev" viewBox="0 0 10 10" @click=${() => this.previous()}>
        <path d="M 6 2 L 4 5 L 6 8" stroke="#fff" fill="none" />
      </svg>
      <svg id="next" viewBox="0 0 10 10" @click=${() => this.next()}>
        <path d="M 4 2 L 6 5 L 4 8" stroke="#fff" fill="none" />
      </svg>
    `;
  }
}

ดูวิธีเพิ่ม Listener เหตุการณ์ในบรรทัดบนปุ่ม SVG ใหม่ของเราได้เลยในเมธอด render ใช้ได้กับทุกกิจกรรม เพียงเพิ่มการเชื่อมโยงของแบบฟอร์ม @eventname=${handler} กับองค์ประกอบ

เพิ่มแอตทริบิวต์ต่อไปนี้ลงในคุณสมบัติ static styles เพื่อจัดรูปแบบปุ่ม

svg {
  position: absolute;
  top: calc(50% - 25px);
  height: 50px;
  cursor: pointer;
}
#next {
  right: 0;
}

สำหรับแถบความคืบหน้า เราจะใช้ตารางกริด CSS ในการจัดรูปแบบกล่องเล็กๆ 1 กล่องสำหรับการ์ดเรื่องราวแต่ละใบ เราสามารถใช้พร็อพเพอร์ตี้ index เพื่อเพิ่มคลาสอย่างมีเงื่อนไขลงในช่องเพื่อระบุว่ามีการ "เห็น" แล้วหรือไม่ หรือไม่ เราใช้นิพจน์แบบมีเงื่อนไข เช่น i <= this.index : 'watched': '' ได้ แต่จะมีรายละเอียดครบถ้วนหากเราเพิ่มคลาสอีก โชคดีที่ lit-html มีคำสั่งที่ชื่อ classMap เพื่อช่วยในเรื่องนี้ ขั้นแรก ให้นำเข้า classMap:

import { classMap } from 'lit/directives/class-map';

และเพิ่มมาร์กอัปต่อไปนี้ที่ด้านล่างของเมธอด render:

<div id="progress">
  ${Array.from(this.children).map((_, i) => html`
    <div
      class=${classMap({watched: i <= this.index})}
      @click=${() => this.index = i}
    ></div>`
  )}
</div>

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

นี่คือรูปแบบใหม่ที่จะเพิ่มลงใน static styles

::slotted(*) {
  position: absolute;
  width: 100%;
  /* Changed this line! */
  height: calc(100% - 20px);
}

#progress {
  position: relative;
  top: calc(100% - 20px);
  height: 20px;
  width: 50%;
  margin: 0 auto;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  grid-gap: 10px;
  align-content: center;
}
#progress > div {
  background: grey;
  height: 4px;
  transition: background 0.3s linear;
  cursor: pointer;
}
#progress > div.watched {
  background: white;
}

การนำทางและแถบความคืบหน้าเสร็จสมบูรณ์ คราวนี้มาเพิ่มลูกเล่นกัน

6. การเลื่อน

เราจะใช้ไลบรารีการควบคุมด้วยท่าทางสัมผัส Hammer.js เพื่อใช้การปัด ค้อนจะตรวจจับท่าทางสัมผัสพิเศษ เช่น การแพน และส่งเหตุการณ์ที่มีข้อมูลที่เกี่ยวข้อง (เช่น เดลต้า X) ที่เราได้ยิน

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

import { state } from 'lit/decorators.js';
import 'hammerjs';

export class StoryViewer extends LitElement {
  // Data emitted by Hammer.js
  @state() _panData: {isFinal?: boolean, deltaX?: number} = {};

  constructor() {
    super();
    this.index = 0;
    new Hammer(this).on('pan', (e: HammerInput) => this._panData = e);
  }
}

เครื่องมือสร้างของคลาส LitElement เป็นอีกตำแหน่งที่ดีในการแนบ Listener เหตุการณ์กับองค์ประกอบโฮสต์ เครื่องมือสร้างค้อนจะนำองค์ประกอบเพื่อตรวจหาท่าทางสัมผัส ในกรณีของเรา นี่คือ StoryViewer เอง หรือ this จากนั้น เราจะบอกให้โปรแกรมตรวจจับ "กระทะ" โดยใช้ Hammer API ท่าทางสัมผัส และตั้งค่าข้อมูลการเลื่อนไปยังพร็อพเพอร์ตี้ _panData ใหม่

เมื่อตกแต่งพร็อพเพอร์ตี้ _panData ด้วย @state แล้ว LitElement จะสังเกตการเปลี่ยนแปลงของ _panData และดำเนินการอัปเดต แต่จะไม่มีแอตทริบิวต์ HTML ที่เชื่อมโยงสำหรับพร็อพเพอร์ตี้ดังกล่าว

ถัดไป มาเพิ่มตรรกะของ update เพื่อใช้ข้อมูลการเลื่อน ดังนี้

// Update is called whenever an observed property changes.
update(changedProperties: PropertyValues) {
  // deltaX is the distance of the current pan gesture.
  // isFinal is whether the pan gesture is ending.
  let { deltaX = 0, isFinal = false } = this._panData;
  // When the pan gesture finishes, navigate.
  if (!changedProperties.has('index') && isFinal) {
    deltaX > 0 ? this.previous() : this.next();
  }
  // We don't want any deltaX when releasing a pan.
  deltaX = isFinal ? 0 : deltaX;
  const width = this.clientWidth;
  Array.from(this.children).forEach((el: Element, i) => {
    // Updated this line to utilize deltaX.
    const x = (i - this.index) * width + deltaX;
    (el as HTMLElement).style.transform = `translate3d(${x}px,0,0)`;
  });

  // Don't forget to call super!
  super.update(changedProperties);
}

ตอนนี้เราสามารถลากการ์ดเรื่องราวไปมาได้แล้ว หากต้องการดำเนินการต่างๆ ให้เป็นไปอย่างราบรื่น เรากลับไปที่ static get styles แล้วเพิ่ม transition: transform 0.35s ease-out; ลงในตัวเลือก ::slotted(*) ดังนี้

::slotted(*) {
  ...
  transition: transform 0.35s ease-out;
}

ตอนนี้เราสามารถปัดอย่างราบรื่น:

การไปยังการ์ดเรื่องราวต่างๆ ด้วยการปัดอย่างราบรื่น

7. เล่นอัตโนมัติ

ฟีเจอร์สุดท้ายที่เราจะเพิ่มคือการเล่นวิดีโออัตโนมัติ เมื่อการ์ดเรื่องราวเข้าสู่จุดโฟกัส เราอยากให้เล่นวิดีโอพื้นหลัง (หากมี) เมื่อการ์ดเรื่องราวออกจากจุดโฟกัส เราควรหยุดวิดีโอชั่วคราว

เราจะใช้สิ่งนี้โดยส่ง "Enter" และ "ออก" เหตุการณ์ที่กำหนดเองในย่อยที่เหมาะสมเมื่อดัชนีมีการเปลี่ยนแปลง ในเดือนStoryCard เราจะรับกิจกรรมดังกล่าวและเล่นหรือหยุดวิดีโอที่มีอยู่ชั่วคราว เหตุใดจึงเลือกส่งกิจกรรมสำหรับเด็กๆ แทนการส่งว่า "เข้าร่วมแล้ว" และ "ออก" วิธีอินสแตนซ์ที่กำหนดไว้ใน StoryCard เมื่อใช้วิธีการ ผู้ใช้คอมโพเนนต์จะไม่มีทางเลือกอื่นนอกจากต้องเขียนองค์ประกอบที่กำหนดเอง หากพวกเขาต้องการเขียนการ์ดเรื่องราวของตนเองด้วยภาพเคลื่อนไหวที่กำหนดเอง เมื่อใช้เหตุการณ์ ครูก็จะแนบ Listener เหตุการณ์ได้

ลองเปลี่ยนโครงสร้างภายในโค้ด index ของ StoryViewer เพื่อใช้ตัวตั้งค่า ซึ่งให้เส้นทางโค้ดที่สะดวกสําหรับส่งเหตุการณ์

class StoryViewer extends LitElement {
  @state() private _index: number = 0
  get index() {
    return this._index
  }
  set index(value: number) {
    this.children[this._index].dispatchEvent(new CustomEvent('exited'));
    this.children[value].dispatchEvent(new CustomEvent('entered'));
    this._index = value;
  }
}

เพื่อปิดฟีเจอร์เล่นอัตโนมัติ เราจะเพิ่ม Listener เหตุการณ์สำหรับ "ที่ป้อน" และ "ออก" ในเครื่องมือสร้าง StoryCard ที่เล่นและหยุดวิดีโอชั่วคราว

อย่าลืมว่าผู้ใช้คอมโพเนนต์อาจให้หรือไม่อนุญาตให้ <story-card> มอบองค์ประกอบวิดีโอในช่องสื่อ และอาจจะไม่มีองค์ประกอบในช่องสื่อเลย เราต้องระวังไม่ให้เรียก play บน img หรือไม่เช่นนั้น

กลับไปที่ Story-card.ts ให้เพิ่มข้อมูลต่อไปนี้

import { query } from 'lit/decorators.js';

class StoryCard extends LitElement {
  constructor() {
    super();

    this.addEventListener("entered", () => {
      if (this._slottedMedia) {
        this._slottedMedia.currentTime = 0;
        this._slottedMedia.play();
      }
    });

    this.addEventListener("exited", () => {
      if (this._slottedMedia) {
        this._slottedMedia.pause();
      }
    });
  }

 /**
  * The element in the "media" slot, ONLY if it is an
  * HTMLMediaElement, such as <video>.
  */
 private get _slottedMedia(): HTMLMediaElement|null {
   const el = this._mediaSlot && this._mediaSlot.assignedNodes()[0];
   return el instanceof HTMLMediaElement ? el : null;
 }

  /**
   * @query(selector) is shorthand for
   * this.renderRoot.querySelector(selector)
   */
  @query("slot[name=media]")
  private _mediaSlot!: HTMLSlotElement;
}

เล่นอัตโนมัติเสร็จสมบูรณ์ ✅

8. ชั่งน้ำหนัก

ตอนนี้เรามีฟีเจอร์ที่จำเป็นทั้งหมดแล้ว เรามาเพิ่มเอฟเฟกต์การปรับขนาดที่น่าใช้กันอีกดีกว่า ย้อนกลับไปที่เมธอด update ของ StoryViewer อีกครั้ง คำนวณบางอย่างเพื่อหาค่าคงที่ scale ซึ่งจะเท่ากับ 1.0 สำหรับผู้เผยแพร่โฆษณาย่อยที่ใช้งานอยู่และ minScale มิเช่นนั้นจะประมวลระหว่าง 2 ค่านี้ด้วย

เปลี่ยนการวนซ้ำในเมธอด update ใน Story-viewer.ts เป็น

update(changedProperties: PropertyValues) {
  // ...
  const minScale = 0.8;
  Array.from(this.children).forEach((el: Element, i) => {
    const x = (i - this.index) * width + deltaX;

    // Piecewise scale(deltaX), looks like: __/\__
    const u = deltaX / width + (i - this.index);
    const v = -Math.abs(u * (1 - minScale)) + 1;
    const scale = Math.max(v, minScale);
    // Include the scale transform
    (el as HTMLElement).style.transform = `translate3d(${x}px,0,0) scale(${scale})`;
  });
  // ...
}

ไม่มีชุดข้อความอื่นแล้วจ้า ในโพสต์นี้เราได้พูดถึงหลายเรื่อง ซึ่งรวมถึงคุณลักษณะ LitElement และ lit-html, องค์ประกอบช่อง HTML และการควบคุมด้วยท่าทางสัมผัส

ดูคอมโพเนนต์นี้ในเวอร์ชันที่สมบูรณ์ได้ที่ https://github.com/PolymerLabs/story-viewer