Jump to content
GreenSock

Search In
  • More options...
Find results that contain...
Find results in...
dukman

GSAP target not found with LitElement

Go to solution Solved by OSUblake,

Recommended Posts

Dear GSAPers,

 

My sincere apologies for the very poor display of code and the lack of codepen example. This is my first post and I was not sure how to provide a more succinct example. Please also note that I am new to TypeScript and am feeling like I am a long way out of my depth so I am not sure how to strip down to the minimal information, or how to make LitElement and its components work in such an environment.

 

All in all, as far as the code goes, the below class DOES work with the exception of GSAP not being able to find the "active" class element, which is a based on a conditional statement in the code below. I can confirm that the HTML inspector tool does indicate that there is at least one element with a class of "active" present on the page, so as far as I understand it, GSAP SHOULD be able to detect it and apply the relevant animation via the animateStatus() function. It is probably also worth mentioning that the console log inside the animateStatus() function prints 'null'.

 

Again, I apologise for the poor display of code. If there is a way to make it clearer please let me know.

 

I would appreciate any help with this.

 

Kind thanks in advance.

import {
  LitElement,
  html,
  customElement,
  property,
} from "@polymer/lit-element";
import gsap from 'gsap';

@customElement("aa-item-button")
export default class ItemButton extends LitElement {
  inputIcon!: string;
  inputText!: string;
  inputLocation!: string;
  inputState!: number;
  inputStatus!: number;
  inputLevel!: number;

  constructor() {
    super()
    // this.animateStatus();
  }

  // element attributes
  static get properties() {
    return {
      inputIcon: {
        type: String,
      },
      inputText: {
        type: String,
      },
      inputLocation: {
        type: String,
      },
      inputState: {
        type: Number,
      },
      inputStatus: {
        type: Number,
      },
      inputLevel: {
        type: Number,
      }
    }
  }
  // // blinking light for active status
  animateStatus() {
    console.log(`Active element is: ${JSON.stringify(this.shadowRoot?.querySelector('.active'))}`)
    gsap.to(".active", {
      backgroundColor: "#3B90FF",
      delay: 0.5,
      duration: 0.5,
      repeat: -1,
      yoyo: true,
    })
  }
  
  render() {
    return html`
      <link
        href="https://fonts.googleapis.com/icon?family=Material+Icons"
        rel="stylesheet"
      />
      <style>
        .active {
          background: #003a85;
          width: 22px;
          height: 12px;
          margin-right: 10px;
          /* box-shadow: #000 0 -1px 7px 1px, inset #460 0 -1px 9px, #7D0 0 2px 12px; */
        }

        .inactive {
          background-color: red;
          width: 22px;
          height: 12px;
          margin-right: 10px;
        }

        .online {
          width: 22px;
          height: 12px;
          background-color: #00ff33;
          margin-right: 10px;
          
          /* box-shadow: #000 0 -1px 7px 1px, inset #460 0 -1px 9px, #7D0 0 2px 12px; */
        }

        .offline {
          width: 22px;
          height: 12px;
          background-color: lightgray;
          margin-right: 10px;
        }

        .button-container {
          position: relative;
          display: flex;
          align-items: center;
          justify-content: center;
          cursor: pointer;
          padding: 1rem;
          /* padding: 1rem;
        width: 302.901px;
        height: 363px; */
        }

        #Button-blank {
          position: relative;
        }

        .contents-container {
          position: absolute;
          height: 91%;
          width: 90%;
        }

        .input-icon {
          display: flex;
          align-items: center;
          justify-content: center;
          color: rgb(130, 130, 130);
          max-width: 100%;
          line-height: 1.1;
          font-size: 200px;
          height: 57%;
        }

        .icon-style {
          height: 100%;
          display: flex;
          align-items: center;
          justify-content: center;
        }

        .icon-container {
          position: absolute;
        }

        .embossed {
          color: #f0f0f0;
          background-color: #666666;
          text-shadow: 1px 4px 4px #555;
          text-align: center;
          background-clip: text;
          -webkit-background-clip: text;
          -moz-background-clip: text;
        }

        .input-text {
          display: flex;
          align-items: center;
          justify-content: center;
          font-family: "Segoe UI";
          font-size: 2rem;
          overflow-wrap: break-word;
          font-weight: 600;
          color: rgb(130, 130, 130);
          max-width: 100%;
          line-height: 1.1;
          height: 20%;
        }

        .text-style {
          height: 100%;
          display: flex;
          align-items: flex-start;
          justify-content: center;
          width: -moz-fit-content;
          width: fit-content;
          width: 85%;
        }

        .location-name {
          color: rgb(130, 130, 130);
          font-weight: 500;
          margin: 0.1rem;
          height: 6%;
        }

        .button-statuses {
          display: flex;
          height: 15%;
          width: 100%;
          color: rgb(130, 130, 130);
          font-weight: 500;
        }

        .left-cell {
          margin-left: 18%;
          width: 45%;
        }

        .status-container {
          display: flex;
          justify-content: flex-start;
        }

        .status-light-container {
          display: flex;
          align-items: center;
          justify-content: center;
        }

        .status-light {
        }

        .status-text {
        }

        .state-container {
          display: flex;
          justify-content: flex-start;
        }

        .state-light-container {
          display: flex;
          align-items: center;
          justify-content: center;
        }

        .state-light {
        }

        .state-text {
        }

        .right-cell {
          width: 45%;
        }

        .level-container {
          display: flex;
          justify-content: flex-start;
        }

        .level-text {
        }
      </style>
      <div class="button-container">
        <!-- Button background -->
        <svg
          id="Button-blank"
          data-name="Lights Button"
          xmlns="http://www.w3.org/2000/svg"
          xmlns:xlink="http://www.w3.org/1999/xlink"
          width="302.901"
          height="363"
        >
          <defs>
            <linearGradient
              id="linear-gradient"
              x1="0.5"
              y1="1"
              x2="0.5"
              gradientUnits="objectBoundingBox"
            >
              <stop offset="0" stop-color="#b1b1b1" />
              <stop offset="0.099" stop-color="#999898" />
              <stop offset="0.211" stop-color="#a0a0a0" />
              <stop offset="0.32" stop-color="#aca6a6" />
              <stop offset="0.411" stop-color="#bebebe" />
              <stop offset="0.415" stop-color="#c5c5c5" />
              <stop offset="0.515" stop-color="#9f9f9f" />
              <stop offset="0.604" stop-color="#908c8c" />
              <stop offset="0.703" stop-color="silver" />
              <stop offset="0.793" stop-color="#a3a3a3" />
              <stop offset="0.905" stop-color="#aeaeae" />
              <stop offset="1" stop-color="#898989" />
            </linearGradient>
            <linearGradient
              id="linear-gradient-3"
              x1="0.5"
              y1="1"
              x2="0.5"
              gradientUnits="objectBoundingBox"
            >
              <stop offset="0" stop-color="#a5a5a5" />
              <stop offset="0.099" stop-color="#d3d3d3" />
              <stop offset="0.211" stop-color="#a0a0a0" />
              <stop offset="0.32" stop-color="#aaa" />
              <stop offset="0.411" stop-color="#bebebe" />
              <stop offset="0.415" stop-color="#c5c5c5" />
              <stop offset="0.515" stop-color="#9f9f9f" />
              <stop offset="0.604" stop-color="#8e8e8e" />
              <stop offset="0.703" stop-color="silver" />
              <stop offset="0.793" stop-color="#a3a3a3" />
              <stop offset="0.905" stop-color="#bcbcbc" />
              <stop offset="1" stop-color="#898989" />
            </linearGradient>
            <linearGradient
              id="linear-gradient-4"
              x1="0.5"
              y1="1"
              x2="0.5"
              gradientUnits="objectBoundingBox"
            >
              <stop offset="0" stop-color="#adadad" />
              <stop offset="0.099" stop-color="#b3b3b3" />
              <stop offset="0.226" stop-color="#f9f9f9" />
              <stop offset="0.328" stop-color="#bebebe" />
              <stop offset="0.428" stop-color="#adadad" />
              <stop offset="0.534" stop-color="#c3c3c3" />
              <stop offset="0.646" stop-color="#f3f3f3" />
              <stop offset="0.74" stop-color="#c6c6c6" />
              <stop offset="0.833" stop-color="#d2d2d2" />
              <stop offset="0.924" stop-color="#c4c4c4" />
              <stop offset="1" stop-color="#bcbcbc" />
            </linearGradient>
          </defs>
          <g id="BG-shadow">
            <g id="Group_3834" data-name="Group 3834">
              <path
                id="Path_8296"
                data-name="Path 8296"
                d="M291.6,363H11.4A11.4,11.4,0,0,1,0,351.6V11.4A11.4,11.4,0,0,1,11.4,0H291.5a11.4,11.4,0,0,1,11.4,11.4V351.5A11.27,11.27,0,0,1,291.6,363Z"
                fill="#878787"
              />
            </g>
          </g>
          <g id="Background">
            <g id="Group_3836" data-name="Group 3836">
              <g id="Group_3835" data-name="Group 3835">
                <path
                  id="Path_8297"
                  data-name="Path 8297"
                  d="M301.5,351.5a10.029,10.029,0,0,1-10,10H11.5a10.029,10.029,0,0,1-10-10V11.5a10.029,10.029,0,0,1,10-10h280a10.029,10.029,0,0,1,10,10Z"
                  fill="url(#linear-gradient)"
                />
                <path
                  id="Path_8298"
                  data-name="Path 8298"
                  d="M301.5,351.5a10.029,10.029,0,0,1-10,10H11.5a10.029,10.029,0,0,1-10-10V11.5a10.029,10.029,0,0,1,10-10h280a10.029,10.029,0,0,1,10,10Z"
                  fill="url(#linear-gradient)"
                />
              </g>
            </g>
            <g id="Group_3838" data-name="Group 3838">
              <g id="Group_3837" data-name="Group 3837">
                <path
                  id="Path_8299"
                  data-name="Path 8299"
                  d="M301.5,351.5a10.029,10.029,0,0,1-10,10H11.5a10.029,10.029,0,0,1-10-10V11.5a10.029,10.029,0,0,1,10-10h280a10.029,10.029,0,0,1,10,10Z"
                  fill="url(#linear-gradient-3)"
                />
              </g>
            </g>
          </g>
          <g id="FG-shadow">
            <g id="Group_3839" data-name="Group 3839">
              <path
                id="Path_8300"
                data-name="Path 8300"
                d="M11.5,341.5a10.029,10.029,0,0,0,10,10h260a10.029,10.029,0,0,0,10-10V21.5a10.029,10.029,0,0,0-10-10H21.5a10.029,10.029,0,0,0-10,10Z"
                fill="#7c7c7c"
              />
            </g>
          </g>
          <g id="Foreground">
            <g id="Group_3840" data-name="Group 3840">
              <path
                id="Path_8301"
                data-name="Path 8301"
                d="M21,347.5H282a5.549,5.549,0,0,0,5.5-5.5V21a5.549,5.549,0,0,0-5.5-5.5H21A5.549,5.549,0,0,0,15.5,21V342A5.549,5.549,0,0,0,21,347.5Z"
                fill="url(#linear-gradient-4)"
              />
            </g>
          </g>
        </svg>
        <!-- Button icon -->
        <div class="contents-container embossed">
          <div class="input-icon material-icons">
            <span class="icon-style">${this.inputIcon}</span>
          </div>
          <!-- Button text -->
          <div class="input-text">
            <div class="text-style">${this.inputText}</div>
          </div>
          <!-- Button statuses -->
          <div class="location-name">(${this.inputLocation.toUpperCase()})</div>
          <div class="button-statuses">
            <div class="left-cell">
              <div class="status-container">
                <div class="status-light-container">
                  <div class="${this.inputStatus ? "online" : "offline"}"></div>
                </div>
                <div class="status-text">
                  ${this.inputStatus == 0 ? "Offline" : "Online"}
                </div>
              </div>
              <div class="state-container">
                <div class="state-light-container">
                  <div class="${this.inputState ? "active" : "inactive"}"></div>${this.inputState ? this.animateStatus() : ``}
                </div>
                <div class="state-text">
                  ${this.inputState == 0 ? "Inactive" : "Active"}
                </div>
              </div>
            </div>
            <div class="right-cell">
              <div class="level-container">
                <div class="level-text">Level: ${this.inputLevel}%</div>
              </div>
            </div>
          </div>
        </div>
      </div>
    `;
  }
}

 

Link to comment
Share on other sites

Welcome to the forums @dukman

 

LitElement uses a shadow DOM, so GSAP's selector engine won't be able to find it, at least not directly. You should use the shadowRoot query selector you just showed..

gsap.to(this.shadowRoot?.querySelector('.active'), {
  ...
})

 

Or some of the query decorators.

https://lit.dev/docs/api/decorators/#query

 

If you need to select a bunch of different elements, GSAP's selector utility can come in handy.

https://greensock.com/docs/v3/GSAP/UtilityMethods/selector()

 

Here's a demo showing the selector utility inside a vanilla web component...

See the Pen 084c410320bcaed334a50760a45ab0ab by osublake (@osublake) on CodePen

 

 

  • Like 3
Link to comment
Share on other sites

Good morning OSUblake,

 

Thank you very much for the warm welcome and your quick and helpful response!

 

Strangely, the below shadowRoot snippet did not work for me, but using the query decorator you suggested has made the issue progress.

gsap.to(this.shadowRoot?.querySelector('.active'), {
  ...
})

 

Below is the updated version with the query decorator implemented. At the moment, both the shadowRoot and query decorator return 'null' even though the element class is present on the page. I attached only the sections that include the changes to minimise the unnecessary clutter.

import {
  LitElement,
  html,
  customElement,
  property,
} from "@polymer/lit-element";
import gsap from 'gsap';
import { query } from "lit-element";

@customElement("aa-item-button")
export default class ItemButton extends LitElement {
  inputIcon!: string;
  inputText!: string;
  inputLocation!: string;
  inputState!: number;
  inputStatus!: number;
  inputLevel!: number;

  @query('.active')
  activeState: any;

  constructor() {
    super()
    // this.attachShadow({mode: "open" }) // Triggers uncaught DOMException
    // this.animateStatus();
  }

  // element attributes

  // // blinking light for active status
  animateStatus() {
    console.log(`Active element is: ${this.shadowRoot?.querySelector('.active')}`) // returns null
    console.log(`Active element is: ${this.activeState}`) // returns null
    gsap.to(this.activeState, {
      backgroundColor: "#3B90FF",
      delay: 0.5,
      duration: 0.5,
      repeat: -1,
      yoyo: true,
    })
  }

 

If I didn't know any better I'd almost think that it's the chicken or the egg scenario where the JavaScript may have loaded before the HTML has and that the "active" element does not yet exist. But then again that doesn't make sense because all the other JavaScript and HTML mix work just fine.

 

I am totally confused with what is happening here...

 

Thanks again!

Link to comment
Share on other sites

Good morning OSUblake,

 

It looks as though the problem in my case was the fundamental error in execution of the LitElement functions. By adding the firstUpdated() method in my project (which I just plain forgot to add!) I got the animation to work.

 

Thank you very much for your help and guidance with not only the problem but also with setting up a codepen!

Please see below the updated and more stripped down version in the pen.

See the Pen PoKpBgm by dukman (@dukman) on CodePen

 

My sincerest thanks to you!

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×