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

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

वीएस कोड इस्तेमाल करने वालों के लिए, 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> एलिमेंट का पैरंट है. इसकी मदद से, कार्ड को हॉरिज़ॉन्टल तौर पर लगाया जा सकेगा और उनमें स्वाइप किया जा सकेगा. हम इसे 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>
    `;
  }
}

सीधे render तरीके में जानें कि हम अपने नए svg बटन पर, इवेंट लिसनर को इनलाइन कैसे जोड़ सकते हैं. यह किसी भी इवेंट के लिए काम करता है. बस किसी एलिमेंट में फ़ॉर्म @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 जेस्चर से कंट्रोल लाइब्रेरी का इस्तेमाल करेंगे. हैमर, पैन जैसे खास जेस्चर का पता लगाता है और इवेंट के बारे में काम की ऐसी जानकारी (जैसे कि डेल्टा 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 है. इसके बाद, हैमर के एपीआई का इस्तेमाल करके हम इसे "पैन" जेस्चर की पहचान करने और पैन की जानकारी को नई _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. ऑटोप्ले

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

हम इंडेक्स में बदलाव होने पर, बच्चों के लिए 'शामिल हुआ' और 'शामिल नहीं है' कस्टम इवेंट भेजकर, इसे लागू करेंगे. 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 कन्स्ट्रक्टर में "वीडियो चलाया गया" और "वीडियो बंद किया गया" के लिए इवेंट लिसनर जोड़ेंगे.

याद रखें कि कॉम्पोनेंट का उपयोगकर्ता, <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.