लाइट-एलिमेंट की मदद से स्टोरी कॉम्पोनेंट बनाएं

1. परिचय

आजकल, स्टोरी एक लोकप्रिय यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट है. सोशल मीडिया और खबरों से जुड़े ऐप्लिकेशन, इन्हें अपनी फ़ीड में इंटिग्रेट कर रहे हैं. इस कोडलैब में, हम lit-element और TypeScript की मदद से एक स्टोरी कॉम्पोनेंट बनाएंगे.

आखिर में, स्टोरी कॉम्पोनेंट ऐसा दिखेगा:

कॉफ़ी की तीन इमेज दिखाने वाला, स्टोरी-व्यूअर कॉम्पोनेंट

हम सोशल मीडिया या खबरों की "स्टोरी" को कार्ड के कलेक्शन के तौर पर देख सकते हैं. इन्हें क्रम से चलाया जाता है. यह कुछ हद तक स्लाइडशो की तरह होती है. दरअसल, स्टोरीज़ एक तरह की स्लाइडशो होती हैं. आम तौर पर, कार्ड में एक इमेज या अपने-आप चलने वाला वीडियो होता है. साथ ही, इसके ऊपर कुछ और टेक्स्ट भी हो सकता है. हम यह बनाएंगे:

सुविधाओं की सूची

  • इमेज या वीडियो बैकग्राउंड वाले कार्ड.
  • स्टोरी पर जाने के लिए, बाईं या दाईं ओर स्वाइप करें.
  • वीडियो अपने-आप चलने की सुविधा.
  • कार्ड में टेक्स्ट जोड़ने या उन्हें अपनी पसंद के मुताबिक बनाने की सुविधा.

इस कॉम्पोनेंट के डेवलपर अनुभव के बारे में बात करें, तो स्टोरी कार्ड को सादे एचटीएमएल मार्कअप में इस तरह से तय करना अच्छा होगा:

<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>

इसलिए, इसे भी सुविधा की सूची में जोड़ते हैं.

सुविधाओं की सूची

  • एचटीएमएल मार्कअप में एक साथ कई कार्ड स्वीकार करना.

इस तरह, कोई भी व्यक्ति सिर्फ़ एचटीएमएल लिखकर, हमारे स्टोरी कॉम्पोनेंट का इस्तेमाल कर सकता है. यह प्रोग्रामर और नॉन-प्रोग्रामर, दोनों के लिए बहुत अच्छा है. साथ ही, यह हर उस जगह काम करता है जहां एचटीएमएल काम करता है. जैसे, कॉन्टेंट मैनेजमेंट सिस्टम, फ़्रेमवर्क वगैरह.

ज़रूरी शर्तें

  • एक शेल, जहां 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 डेकोरेटर, हमारे लिए कस्टम एलिमेंट को रजिस्टर करने का काम करता है. अब यह सही समय है, जब आपको experimentalDecorators फ़्लैग का इस्तेमाल करके, अपने tsconfig में डेकोरेटर चालू करने चाहिए. अगर स्टार्टर रेपो का इस्तेमाल किया जा रहा है, तो यह पहले से ही चालू है.

story-card.ts में यह कोड डालें:

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

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

अब <story-card> का इस्तेमाल कस्टम एलिमेंट के तौर पर किया जा सकता है. हालांकि, अब भी दिखाने के लिए कोई कॉन्टेंट मौजूद नहीं है. किसी एलिमेंट के इंटरनल स्ट्रक्चर को तय करने के लिए, render इंस्टेंस मेथड तय करें. यहां हम lit-html के html टैग का इस्तेमाल करके, एलिमेंट के लिए टेंप्लेट उपलब्ध कराएंगे.

इस कॉम्पोनेंट के टेंप्लेट में क्या होना चाहिए? उपयोगकर्ता को दो चीज़ें उपलब्ध कराने का विकल्प मिलना चाहिए: मीडिया एलिमेंट और ओवरले. इसलिए, हम इनमें से हर एक के लिए एक <slot> जोड़ेंगे.

स्लॉट की मदद से यह तय किया जाता है कि कस्टम एलिमेंट के चिल्ड्रन को कैसे रेंडर किया जाना चाहिए. ज़्यादा जानकारी के लिए, स्लॉट इस्तेमाल करने के बारे में यहां पूरी जानकारी दी गई है.

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

मीडिया एलिमेंट को अलग स्लॉट में रखने से, हमें उस एलिमेंट को टारगेट करने में मदद मिलेगी. जैसे, फ़ुल-ब्लीड स्टाइलिंग जोड़ना और वीडियो अपने-आप चलने की सुविधा चालू करना. दूसरे स्लॉट (कस्टम ओवरले के लिए) को कंटेनर एलिमेंट में रखें, ताकि हम बाद में कुछ डिफ़ॉल्ट पैडिंग दे सकें.

अब <story-card> कॉम्पोनेंट का इस्तेमाल इस तरह किया जा सकता है:

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

हालांकि, यह बहुत खराब दिखता है:

बिना स्टाइल वाला स्टोरी-व्यूअर, जिसमें कॉफ़ी की तस्वीर दिख रही है

स्टाइल जोड़ना

चलिए, कुछ स्टाइल जोड़ते हैं. lit-element की मदद से, हम ऐसा स्टैटिक styles प्रॉपर्टी को तय करके करते हैं. साथ ही, css से टैग की गई टेंप्लेट स्ट्रिंग को वापस लाते हैं. यहां लिखा गया सीएसएस कोड सिर्फ़ हमारे कस्टम एलिमेंट पर लागू होता है! इस तरह, शैडो डीओएम के साथ सीएसएस का इस्तेमाल करना बहुत अच्छा होता है.

आइए, स्लॉट किए गए मीडिया एलिमेंट को स्टाइल करके <story-card> को कवर करें. हम यहां दूसरे स्लॉट में मौजूद एलिमेंट के लिए, कुछ बेहतर फ़ॉर्मैटिंग उपलब्ध करा सकते हैं. इस तरह, कॉम्पोनेंट का इस्तेमाल करने वाले लोग कुछ <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>s का पैरंट है. इसकी मदद से, कार्ड को हॉरिज़ॉन्टल तरीके से लेआउट किया जाएगा. साथ ही, हम एक कार्ड से दूसरे कार्ड पर स्वाइप कर पाएंगे. हम इसे उसी तरह से शुरू करेंगे जैसे हमने 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> को ऐब्सलूट पोज़िशनिंग दे सकते हैं. साथ ही, उन्हें उनके इंडेक्स के हिसाब से ट्रांसलेट कर सकते हैं. हम :host सिलेक्टर का इस्तेमाल करके, <story-viewer> एलिमेंट को टारगेट कर सकते हैं.

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

अभी देखे जा रहे कार्ड को ट्रैक करने के लिए, StoryViewer क्लास में एक इंस्टेंस वैरिएबल index जोड़ें. इसे LitElement के @property से डेकोरेट करने पर, कॉम्पोनेंट की वैल्यू में बदलाव होने पर, यह फिर से रेंडर होगा.

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

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

हर कार्ड को हॉरिज़ॉन्टल तरीके से अपनी जगह पर ट्रांसलेट किया जाना चाहिए. आइए, इन अनुवादों को lit-element के 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 पर जाएं और स्टोरी-कार्ड के बाकी एलिमेंट से टिप्पणी हटाएं. अब, आइए इन्हें इस तरह से बनाएं कि हम इन पर नेविगेट कर सकें!

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, एलिमेंट में इवेंट लिसनर जोड़ने की सुविधा देता है. हम बटन रेंडर कर सकते हैं और एक ही समय में क्लिक लिसनर जोड़ सकते हैं.

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

देखें कि हम अपने नए SVG बटन पर, render तरीके में इवेंट लिसनर को इनलाइन कैसे जोड़ सकते हैं. यह सुविधा किसी भी इवेंट के लिए काम करती है. बस फ़ॉर्म @eventname=${handler} को किसी एलिमेंट से बाइंड करें.

बटन को स्टाइल करने के लिए, static styles प्रॉपर्टी में ये वैल्यू जोड़ें:

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

प्रोग्रेस बार के लिए, हम सीएसएस ग्रिड का इस्तेमाल करेंगे. इससे छोटे बॉक्स को स्टाइल किया जा सकेगा. हर स्टोरी कार्ड के लिए एक बॉक्स होगा. हम 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 क्लास का कंस्ट्रक्टर, होस्ट एलिमेंट पर इवेंट लिसनर अटैच करने के लिए एक और बेहतरीन जगह है. Hammer कंस्ट्रक्टर, जेस्चर का पता लगाने के लिए एक एलिमेंट लेता है. हमारे मामले में, यह StoryViewer या this है. इसके बाद, हम Hammer के एपीआई का इस्तेमाल करके, उसे "पैन" जेस्चर का पता लगाने के लिए कहते हैं. साथ ही, पैन की जानकारी को नई _panData प्रॉपर्टी पर सेट करते हैं.

_panData प्रॉपर्टी को @state से डेकोरेट करने पर, LitElement _panData में होने वाले बदलावों को मॉनिटर करेगा और अपडेट करेगा. हालांकि, प्रॉपर्टी के लिए कोई एचटीएमएल एट्रिब्यूट नहीं होगा.

इसके बाद, पैन डेटा का इस्तेमाल करने के लिए, 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 पर वापस जाएं और ::slotted(*) सिलेक्टर में transition: transform 0.35s ease-out; जोड़ें:

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

अब हमारे पास स्वाइप करने की सुविधा है:

आसानी से स्वाइप करके, स्टोरी-कार्ड के बीच नेविगेट करना

7. ऑटोप्ले

हम अपने-आप वीडियो चलने की सुविधा भी जोड़ेंगे. जब कोई स्टोरी कार्ड फ़ोकस में आता है, तो हम चाहते हैं कि अगर बैकग्राउंड वीडियो मौजूद है, तो वह चले. जब कोई स्टोरी कार्ड फ़ोकस से हट जाए, तो हमें उसका वीडियो रोकना चाहिए.

हम इस सुविधा को लागू करने के लिए, इंडेक्स में बदलाव होने पर 'entered' और 'exited' कस्टम इवेंट को सही बच्चों के लिए भेजेंगे. StoryCard में, हमें वे इवेंट मिलेंगे और हम मौजूदा वीडियो को चलाएंगे या रोकेंगे. StoryCard पर तय की गई 'entered' और 'exited' इंस्टेंस के तरीकों को कॉल करने के बजाय, बच्चों के लिए इवेंट क्यों भेजे जाते हैं? तरीकों के साथ, कॉम्पोनेंट इस्तेमाल करने वाले लोगों के पास कस्टम एलिमेंट लिखने के अलावा कोई विकल्प नहीं होगा. ऐसा तब होगा, जब उन्हें कस्टम ऐनिमेशन के साथ अपना स्टोरी कार्ड लिखना होगा. इवेंट की मदद से, वे सिर्फ़ एक इवेंट लिसनर अटैच कर सकते हैं!

आइए, StoryViewer की index प्रॉपर्टी को फिर से फ़ैक्टर करते हैं, ताकि सेटर का इस्तेमाल किया जा सके. इससे इवेंट भेजने के लिए, कोड का आसान पाथ मिलता है:

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

अपने-आप चलने की सुविधा को पूरा करने के लिए, हम StoryCard कंस्ट्रक्टर में "entered" और "exited" के लिए इवेंट लिसनर जोड़ेंगे. ये वीडियो को चलाने और रोकने का काम करते हैं.

ध्यान रखें कि कॉम्पोनेंट का इस्तेमाल करने वाला व्यक्ति, मीडिया स्लॉट में <story-card> वीडियो एलिमेंट दे भी सकता है और नहीं भी दे सकता. ऐसा भी हो सकता है कि वे मीडिया स्लॉट में कोई एलिमेंट न दिखाएं. हमें यह ध्यान रखना होगा कि हम किसी इमेज या शून्य पर play को कॉल न करें.

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. बदलाव लाना

अब हमारे पास सभी ज़रूरी सुविधाएं हैं. इसलिए, आइए एक और सुविधा जोड़ते हैं: स्केल करने का इफ़ेक्ट. आइए, StoryViewer के update तरीके पर एक बार फिर से वापस जाते हैं. scale कॉन्स्टेंट में वैल्यू पाने के लिए, कुछ गणितीय कैलकुलेशन की जाती हैं. यह ऐक्टिव चाइल्ड के लिए 1.0 और अन्य के लिए minScale के बराबर होगा. साथ ही, इन दोनों वैल्यू के बीच इंटरपोलेट भी करेगा.

story-viewer.ts में update तरीके में लूप को बदलकर यह करें:

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 की कुछ सुविधाएं, एचटीएमएल स्लॉट एलिमेंट, और जेस्चर कंट्रोल.

इस कॉम्पोनेंट के पूरे वर्शन के लिए, https://github.com/PolymerLabs/story-viewer पर जाएं.