Reimplementing Deprecated

HTML Elements

with Web Components

(An Ode to GeoCities)

Who Am I?

Alex Riviere

  • Senior Frontend Developer
  • Co-Host of Enjoy the Vue Podcast
  • Twitch Streamer
  • Atlanta Vue.js Meetup and PyATL Co-Organizer

Let's take a trip back in time....

A Brief History of the Web

  • Nov. 1995 - HTML 2.0 RFC published
  • Aug. 1996 - IE3 released with CSS support
  • Jan. 1997 - HTML 3.2 Spec published

A Brief History of the Web

This is the time period that gave us

<marquee> <blink> <font> <center>

and other well loved tags

A Brief History of the Web

Dec. 1997

HTML 4.0 RFC published

deprecates a lot of tags

A Brief History of the Web

  • Mid 1995 - BHI (Beverly Hills Internet) is formed
    • 2MB of free web hosting
    • users could join a "neighborhood"
    • focused on community
  • Dec 1995 - BHI rebrands to

GeoCities

GeoCities

A webpage with angled 3d clipart declaring "Dr Dale's Cyber Clinic" with a link that says "Press HERE to enter the Clinic"
A webpage with a repeating background of an orange planet with the text "Welcome" rotating in the header. The page contains the following text in a light blue font, "AS-SALAAM-ALAKIUM Dear Internet Person, You have Entered the World of Hakim please come in and enjoy at your own risk. Hakim's World. My Pages include: The latest News, Graphics, Information from the NBA and NFL. Keep track of world events with CNN."
A website with 3d clip art declaring "Beautiful World" as the title with some low resolution flowers above it. A weird looking pink and blue graphic below that declares "Welcome!"
A webpage with a bubbly background with the following text: "Creatures of the Sea  Ocean Clipart  Here is some fish and sea life clipart I created while developing  my Under the Sea Unit.  Please feel free to use any of it you would like.  I would appreciate a link back.  Please use the following logo for your link."
A website that is titled with gold 3d clipart that says "Babetta's Place" and has a small dove in flight below it.

Let's jump ahead a bit

2013 - customElements Proposal

Make your own html elements!

2018 - customElements available in most browsers

(We can probably use this now in 2022.)

Let's jump ahead a bit

2021 - I got bored and nostalgic

(This will not end well.)

GeoElements

GeoElements

  • Experience the joy of HTML3 with good accessibility
  • focus on your html, and nothing else
  • (it's probably a bad idea to use this.)

How to make

Custom Elements

Let's make a <blink> Tag

class GeoElementBlink extends HTMLElement{
  constructor(){
    super();
    this.attachShadow({mode:'open'});
    const style = document.createElement('style');
    const slot = document.createElement('slot');
    style.textContent = `
    :host{ font-size:inherit; line-height:inherit;}

    slot{ animation: 2s linear infinite condemned_blink_effect; }

    @keyframes condemned_blink_effect { 0% {visibility: hidden;} 50% {visibility: hidden;} 100% {visibility: visible;} }

    @media (prefers-reduced-motion) {
      slot { animation:none; text-shadow: 0 0 0.1rem red, 0 0 0.2rem orange, 0 0 0.3rem yellow, 0 0 0.4rem green, 0 0 0.5rem blue, 0 0 0.6rem indigo, 0 0 0.7rem violet;}
   }`;
    this.shadowRoot.append(style,slot);
  }
}

customElements.define('ge-blink', GeoElementBlink);

Let's make a <blink> Tag

quick review

  • create a by extending an HTMLElement
  • always call super() first in your constructor
  • use this.attachShadow({mode:'open'}) to attach shadowDOM

  • use this.shadowRoot.append to add elements to the shadowDOM

  • make sure to add a slot element to display child content.

  • register your new element with window.customElements.define

Let's talk about attributes

Let's make a <background> Tag

class GeoElementBackground extends HTMLElement {
  static get observedAttributes() {
    return ["background", "bgcolor", "inline"];
  }

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case "background":
        this.updateImage(newValue);
        break;
      case "bgcolor":
        this.updateColor(newValue);
        break;
      case "inline":
        this.updateInline(newValue);
        break;
    }
  }

  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    this.imageStyle = document.createElement("style");
    this.colorStyle = document.createElement("style");
    this.inlineStyle = document.createElement("style");
    const slot = document.createElement("slot");
    this.shadowRoot.append(
      this.imageStyle,
      this.colorStyle,
      this.inlineStyle,
      slot
    );

    this.updateImage();
    this.updateColor();
    this.updateInline();
  }

  updateStyle(type, value) {
    this[type + "Style"].textContent = value;
  }

  updateImage(value) {
    if (!value) {
      this.updateStyle("image", "");
    } else {
      this.updateStyle("image", `:host{background-image:url(${value});}`);
    }
  }

  updateColor(value) {
    if (!value) {
      this.updateStyle("color", `:host{background-color: transparent;}`);
    } else {
      this.updateStyle("color", `:host{background-color: ${value};}`);
    }
  }

  updateInline(value) {
    if (!value) {
      this.updateStyle(
        "inline",
        `:host{display:block; min-height: 100vh; min-width: 100vw;}`
      );
    } else {
      this.updateStyle("inline", `:host{display:inline-block;}`);
    }
  }
}

customElements.define("ge-background", GeoElementBackground);

Let's make a <background> Tag

quick review

  • static get observedAttributes() returns an array of attributes that should be watched for changes
  • attributeChangedCallback will be called each time an observedAttribute is modified.

Let's Make a

<marquee>

<marquee> is weird

<marquee> is weird

<marquee> is weird

Let's make a <simple-marquee>

Let's make a <simple-marquee>

class SimpleMarquee extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    const style = document.createElement("style");
    this.divElement = document.createElement("div");
    this.divElement.append(document.createElement("slot"));
    style.textContent = `:host {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-align: initial;
    white-space: nowrap;
}
div{
  display: inline-block;
    overflow: hidden;
    text-align: initial;
    white-space: nowrap;
}
`;
    this.shadowRoot.append(style, this.divElement);
    const animation = this.divElement.animate(
      [
        { transform: `translateX(${this.clientWidth}px)` },
        { transform: `translateX(-${this.divElement.scrollWidth}px)` }
      ],
      {
        duration: (this.offsetWidth+this.divElement.scrollWidth) / (6/85),
        iterations: Infinity
      }
    );
  }
}

customElements.define("simple-marquee", SimpleMarquee);

Let's make a <simple-marquee>

Let's make a <simple-marquee>

  • connectedCallback
  • adoptedCallback
  • disconnectedCallback

Let's FIX <simple-marquee>

class SimpleMarquee extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    const style = document.createElement("style");
    this.divElement = document.createElement("div");
    this.divElement.append(document.createElement("slot"));
    style.textContent = `:host {
    display: inline-block;
    max-width: 100%;
    overflow: hidden;
    text-align: initial;
    white-space: nowrap;
}
div{
  display: inline-block;
    overflow: hidden;
    text-align: initial;
    white-space: nowrap;
}
`;
    this.shadowRoot.append(style, this.divElement);
    this.currentAnimation = null;
  }

  startAnimation() {
    if (this.currentAnimation) {
      this.currentAnimation.cancel();
    }

    this.currentAnimation = this.divElement.animate(
      [
        { transform: `translateX(${this.clientWidth}px)` },
        { transform: `translateX(-${this.divElement.scrollWidth}px)` }
      ],
      {
        duration: (this.offsetWidth + this.divElement.scrollWidth) / (6 / 85),
        iterations: Infinity
      }
    );
  }

  connectedCallback() {
    this.startAnimation();
  }

  adoptedCallback() {
    this.startAnimation();
  }

  disconnectedCallback() {
    if (this.currentAnimation) {
      this.currentAnimation.cancel();
    }
    delete this.currentAnimation;
  }
}

customElements.define("simple-marquee", SimpleMarquee);

Let's FIX <simple-marquee>

Let's Review

  • Use the connectedCallback method to handle being added to the dom
  • Use the adoptedCallback to handle being moved from one document to another (generally between iframes)
  • Use disconnectedCallback to clean up anything before an element is destroyed

Conclusion

  • The web should be fun
  • The web should be accessible
  • Go make dumb things
  • Make dumb things for everyone.

Thank you.

For More Terrible Things: