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
มีการตั้งค่าสภาพแวดล้อมด้วยองค์ประกอบที่มีแสงน้อยและ 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