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

เราอาจมองว่า "สตอรี่" ในโซเชียลมีเดียหรือข่าวเป็นชุดการ์ดที่เล่นตามลำดับ คล้ายกับสไลด์โชว์ จริงๆ แล้วเรื่องราวก็คือสไลด์โชว์นั่นเอง โดยปกติแล้วการ์ดจะมีรูปภาพหรือวิดีโอเล่นอัตโนมัติเป็นหลัก และอาจมีข้อความเพิ่มเติมอยู่ด้านบน สิ่งที่เราจะสร้างมีดังนี้
รายการฟีเจอร์
- การ์ดที่มีพื้นหลังเป็นรูปภาพหรือวิดีโอ
- ปัดไปทางซ้ายหรือขวาเพื่อไปยังส่วนต่างๆ ของสตอรี่
- วิดีโอที่เล่นอัตโนมัติ
- ความสามารถในการเพิ่มข้อความหรือปรับแต่งการ์ด
ในส่วนของประสบการณ์นักพัฒนาซอฟต์แวร์ของคอมโพเนนต์นี้ เราอยากให้ระบุการ์ดเรื่องราวในมาร์กอัป 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 ใช้ได้ เช่น ระบบจัดการเนื้อหา เฟรมเวิร์ก ฯลฯ
ข้อกำหนดเบื้องต้น
- เชลล์ที่คุณเรียกใช้
gitและnpmได้ - โปรแกรมแก้ไขข้อความ
2. การตั้งค่า
เริ่มต้นด้วยการโคลนที่เก็บนี้: story-viewer-starter
git clone git@github.com:PolymerLabs/story-viewer-starter.git
มีการตั้งค่าสภาพแวดล้อมด้วย Lit-Element และ TypeScript แล้ว เพียงติดตั้งการอ้างอิงด้วยคำสั่งต่อไปนี้
npm i
สำหรับผู้ใช้ VS Code ให้ติดตั้งส่วนขยาย lit-plugin เพื่อรับการเติมข้อความอัตโนมัติ การตรวจสอบประเภท และการวิเคราะห์ซอร์สโค้ดของเทมเพลต lit-html
เริ่มสภาพแวดล้อมในการพัฒนาซอฟต์แวร์โดยเรียกใช้คำสั่งต่อไปนี้
npm run dev
คุณพร้อมที่จะเริ่มเขียนโค้ดแล้ว
3. คอมโพเนนต์ <story-card>
เมื่อสร้างคอมโพเนนต์แบบผสม บางครั้งการเริ่มต้นด้วยคอมโพเนนต์ย่อยที่เรียบง่ายกว่าและค่อยๆ สร้างขึ้นจะง่ายกว่า มาเริ่มสร้าง <story-card> กันเลย โดยควรแสดงวิดีโอหรือรูปภาพแบบเต็มขอบได้ ผู้ใช้ควรปรับแต่งเพิ่มเติมได้ เช่น ใช้ข้อความซ้อนทับ
ขั้นตอนแรกคือการกำหนดคลาสของคอมโพเนนต์ ซึ่งขยาย LitElement ตัวตกแต่ง customElement จะจัดการการลงทะเบียนองค์ประกอบที่กำหนดเองให้เรา ตอนนี้เป็นเวลาที่เหมาะที่จะเปิดใช้ Decorator ใน 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> สำหรับแต่ละรายการ
สล็อตคือวิธีที่เราใช้ระบุว่าควรแสดงผล children ของ Custom Element อย่างไร ดูข้อมูลเพิ่มเติมได้ที่คำแนะนำแบบทีละขั้นตอนเกี่ยวกับการใช้สล็อต
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>
แต่ดูแย่มาก

การเพิ่มสไตล์
มาเพิ่มสไตล์กัน ใน LitElement เราจะทำเช่นนั้นโดยการกำหนดพร็อพเพอร์ตี้ 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. คอมโพเนนต์ <story-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วิธีการวงจรของ lit-element กัน เมธอดอัปเดตจะทํางานทุกครั้งที่พร็อพเพอร์ตี้ที่สังเกตได้ของคอมโพเนนต์นี้มีการเปลี่ยนแปลง โดยปกติแล้ว เราจะค้นหาสล็อตและวนซ้ำ 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 แล้วยกเลิกการแสดงความคิดเห็นในองค์ประกอบที่เหลือของการ์ดเรื่องราว ตอนนี้มาทำให้เราไปยังหน้าเว็บเหล่านั้นได้กัน
5. แถบความคืบหน้าและการนำทาง
จากนั้นเราจะเพิ่มวิธีไปยังส่วนต่างๆ ของการ์ดและแถบความคืบหน้า
มาเพิ่มฟังก์ชันตัวช่วยบางอย่างลงใน StoryViewer เพื่อไปยังส่วนต่างๆ ของเรื่องราวกัน โดยจะตั้งค่าดัชนีให้เราในขณะที่จำกัดให้อยู่ในช่วงที่ถูกต้อง
ใน story-viewer.ts ให้เพิ่มข้อมูลต่อไปนี้ใน StoryViewer class
/** 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 Grid เพื่อจัดรูปแบบกล่องเล็กๆ โดยมีกล่องหนึ่งกล่องสำหรับแต่ละการ์ดเรื่องราว เราสามารถใช้พร็อพเพอร์ตี้ 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 เพื่อติดตั้งใช้งานการปัด Hammer จะตรวจจับท่าทางพิเศษ เช่น การแพน และส่งเหตุการณ์พร้อมข้อมูลที่เกี่ยวข้อง (เช่น เดลต้า X) ที่เราใช้ได้
ต่อไปนี้คือวิธีใช้ Hammer เพื่อตรวจหาการแพน และอัปเดตองค์ประกอบโดยอัตโนมัติเมื่อใดก็ตามที่เกิดเหตุการณ์การแพน
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 เหตุการณ์ในองค์ประกอบโฮสต์เอง ตัวสร้าง Hammer จะใช้องค์ประกอบในการตรวจหาท่าทางสัมผัส ในกรณีของเราคือ StoryViewer เอง หรือ this จากนั้นใช้ API ของ Hammer เพื่อบอกให้ตรวจหาท่าทางสัมผัส "แพน" และตั้งค่าข้อมูลการแพนลงในพร็อพเพอร์ตี้ _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. เล่นอัตโนมัติ
ฟีเจอร์สุดท้ายที่เราจะเพิ่มคือการเล่นวิดีโออัตโนมัติ เมื่อการ์ดเรื่องราวเข้าสู่โฟกัส เราต้องการให้วิดีโอพื้นหลังเล่นหากมี เมื่อการ์ดเรื่องราวไม่อยู่ในโฟกัส เราควรหยุดวิดีโอของการ์ดชั่วคราว
เราจะดำเนินการนี้โดยส่งเหตุการณ์ที่กำหนดเอง "เข้า" และ "ออก" ในองค์ประกอบย่อยที่เหมาะสมทุกครั้งที่ดัชนีเปลี่ยนแปลง ใน StoryCard เราจะได้รับเหตุการณ์เหล่านั้นและเล่นหรือหยุดวิดีโอที่มีอยู่ชั่วคราว เหตุใดจึงเลือกที่จะส่งเหตุการณ์ในองค์ประกอบย่อยแทนที่จะเรียกเมธอดอินสแตนซ์ "entered" และ "exited" ที่กำหนดไว้ใน StoryCard หากใช้วิธีการดังกล่าว ผู้ใช้คอมโพเนนต์จะไม่มีทางเลือกอื่นนอกจากต้องเขียนองค์ประกอบที่กำหนดเองหากต้องการเขียนการ์ดเรื่องราวของตนเองด้วยภาพเคลื่อนไหวที่กำหนดเอง โดยใช้เหตุการณ์ ผู้ใช้จะแนบ Listener เหตุการณ์ ได้
มาปรับโครงสร้างพร็อพเพอร์ตี้ StoryViewer ของ index เพื่อใช้ Setter ซึ่งจะให้เส้นทางโค้ดที่สะดวกสำหรับการส่งเหตุการณ์
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 เหตุการณ์สำหรับ "entered" และ "exited" ในเครื่องมือสร้าง StoryCard ที่เล่นและหยุดวิดีโอชั่วคราวเพื่อปิดท้ายฟีเจอร์เล่นอัตโนมัติ
โปรดทราบว่าผู้ใช้คอมโพเนนต์อาจให้หรือไม่ให้ <story-card> มีองค์ประกอบวิดีโอในช่องสื่อก็ได้ หรืออาจไม่แสดงองค์ประกอบในช่องสื่อเลยก็ได้ เราต้องระมัดระวังไม่เรียกใช้ play ใน img หรือใน null
กลับไปที่ 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