“This is the 21st day of my participation in the First Challenge 2022. For details: First Challenge 2022”

Today, I’ll show you how to create a Web component that allows your users to decide if they want to play giFs! Let’s get started.

Some lovely test data

The GIF used here is this cute interaction between a baby camel and a cat:

Wow, that’s so cute! I could watch this all day

Building Web Components

For this Web component, we need a few things:

  • Canvas (where the “thumbnail” is)
  • An image (actual GIF)
  • A label labeled “GIF”
  • Some modelling

Let’s do this:

const noAutoplayGifTemplate = document.createElement('template')
noAutoplayGifTemplate.innerHTML = `
<style>
.no-autoplay-gif {
  --size: 30px;
  cursor: pointer;
  position: relative;
}

.no-autoplay-gif .gif-label {
  border: 2px solid # 000;
  background-color: #fff;
  border-radius: 100%;
  width: var(--size);
  height: var(--size);
  text-align: center;
  font: bold calc(var(--size) * 0.4) /var(--size) sans-serif;
  position: absolute;
  top: calc(50% - var(--size) / 2);
  left: calc(50% - var(--size) / 2);
}

.no-autoplay-gif .hidden {
  display: none;
}
</style>
<div class="no-autoplay-gif">
  <canvas />
  <span class="gif-label" aria-hidden="true">GIF</span>
  <img class="hidden">
</div>`
Copy the code

Next, we’ll create a class derived from HTMLElement. This class will later include play/stop switching behavior.

class NoAutoplayGif extends HTMLElement {
  constructor() {
    super(a)// Add Settings here
  }

  loadImage() {
    // Add render here
  }

  static get observedAttributes() {
    return ['src'.'alt'];
  }

  attributeChangedCallback(name, oldVal, newVal) {
    if(oldVal ! == newVal || oldVal ===null) {
      this.loadImage()
    }
  }
}
Copy the code

Here are some more boilerplate: an empty render function that loads the image and displays the thumbnail, a constructor and some Web component-specific methods.

Okay, that’s a lot of code. Let me explain.

The loadImage function is not called automatically; we need to call it ourselves. This function attributeChangedCallback lets us define what happens when observedAttributes change to any given attribute. In this case: load the image and display it. The browser does something like this:

  • Encountering a Web component
  • Call its constructor (callconstructor())
  • Set its properties one by one to the Settings in the DOM (so,src="llama.gif"call.setAttribute('src', 'llama.gif')
  • attributeChangedCallbackExecutes on each changed property

When the constructor is checked in, these properties are initially empty and will be populated later. If we need one or more properties to actually render, then it makes no sense to call the loadImage function if we know those properties don’t exist. So we don’t call it in the constructor, but only when there is a possibility that a property exists. **

To complete the boilerplate, let’s define this class as our custom Web component:

class NoAutoplayGif extends HTMLElement {
  // ...
}

window.customElements.define('no-autoplay-gif', NoAutoplayGif)
Copy the code

We can now use this component like this:

<no-autoplay-gif 
  src="..." 
  alt="Llama and cat" 
/>
Copy the code

logic

Here’s the fun part. We need to add noAutoplayGifTemplate as the component’s Shadow DOM. SRC this already renders the DOM, but we still can’t do much without the Andalt attribute. So we’ve only collected elements from the Shadow DOM that we’ll need later, and we’ve attached a click listener to switch the start/stop mode.

class NoAutoplayGif extends HTMLElement {
  constructor() {
    super(a)// Add shadow DOM
    this._shadowRoot = this.attachShadow({ mode: 'open' })

    // Add the template from above
    this._shadowRoot.appendChild(
      noAutoplayGifTemplate.content.cloneNode(true))// We will need these later
    this.canvas = this._shadowRoot.querySelector('canvas')
    this.img = this._shadowRoot.querySelector('img')
    this.label = this._shadowRoot.querySelector('.gif-label')
    this.container = this._shadowRoot.querySelector('.no-autoplay-gif')

    // Make the whole thing clickable
    this._shadowRoot.querySelector('.no-autoplay-gif').addEventListener('click'.() = > {
      this.toggleImage()
    })
  }

  // ...
}
Copy the code

To avoid undefined method errors, we also added these three methods:

class NoAutoplayGif extends HTMLElement {
  // ...
  toggleImage(force = undefined) {
    this.img.classList.toggle('hidden', force)

    // We need to check for undefined values, as JS does a distinction here.
    // We cannot simply negate a given force value (i.e. hiding one thing and unhiding another)
    // as an undefined value would actually toggle the img, but
    // always hide the other two, because ! undefined == true
    this.canvas.classList.toggle('hidden', force ! = =undefined? ! force :undefined)
    this.label.classList.toggle('hidden', force ! = =undefined? ! force :undefined)}start() {
    this.toggleImage(false)}stop() {
    this.toggleImage(true)}// ...
}
Copy the code

The start/stop methods allow us to force start or stop giFs. In theory, we can now do this:

const gif = document.querySelector('no-autoplay-gif')
gif.start()
gif.stop()
gif.toggleImage()
Copy the code

Finally, we can add an image loading section. Let’s do some verification first:

class NoAutoplayGif extends HTMLElement {
  // ...
  loadImage() {
    const src = this.getAttribute('src')
    const alt = this.getAttribute('alt')

    if(! src) {console.warn('A source gif must be given')
      return
    }

    if(! src.endsWith('.gif')) {
      console.warn('Provided src is not a .gif')
      return
    }

    // More stuff
  }
  // ...
}
Copy the code

As a final step, we can load the image, set some widths and heights and use the canvas:

class NoAutoplayGif extends HTMLElement {
  // ...
  loadImage() {
    // Validation

    this.img.onload = event= > {
      const width = event.currentTarget.width
      const height = event.currentTarget.height

      // Set width and height of the entire thing
      this.canvas.setAttribute('width', width)
      this.canvas.setAttribute('height', height)
      this.container.setAttribute('style'.`
        width: ${width}px;
        height: ${height}px;
      `)

      // "Draws" the gif onto a canvas, i.e. the first
      // frame, making it look like a thumbnail.
      this.canvas.getContext('2d').drawImage(this.img, 0.0)}// Trigger the loading
    this.img.src = src
    this.img.alt = alt
  }
  // ...
}
Copy the code

We’re done!

The results of

Demo address: haiyong.site/ GIF