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 ใช้งานได้ เช่น ระบบจัดการเนื้อหา เฟรมเวิร์ก ฯลฯ
ข้อกำหนดเบื้องต้น
- Shell ที่คุณเรียกใช้
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
จะจัดการลงทะเบียนองค์ประกอบที่กําหนดเองให้เรา ตอนนี้เป็นโอกาสที่ดีในการตรวจสอบว่าได้เปิดใช้เครื่องมือตกแต่งใน 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. คอมโพเนนต์ <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
/** 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) ที่เราได้ยิน
วิธีที่เราใช้ 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 เหตุการณ์กับองค์ประกอบโฮสต์ เครื่องมือสร้างค้อนจะนำองค์ประกอบเพื่อตรวจหาท่าทางสัมผัส ในกรณีของเรา นี่คือ 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 เหตุการณ์ได้
ลองเปลี่ยนโครงสร้างภายในโค้ด 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;
}
}
เราจะเพิ่ม Listeners เหตุการณ์สําหรับ "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. ชั่งน้ำหนัก
เมื่อเรามีฟีเจอร์ที่จำเป็นทั้งหมดแล้ว เรามาเพิ่มอีก 1 ฟีเจอร์กัน นั่นก็คือเอฟเฟกต์การปรับขนาด กลับไปที่เมธอด 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