CSS - UV Gloss finish example

by PhilQ

HTML

<div id="container">
  <div
    class="card"
   ...

SCSS

*, *::before, *::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  background: #20262E;
  padding: 50px;
  font-family: Helvetica;
}

#container {
  position: relative;
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  margin: 0 auto;
  width: 400px;
  height: 225px;
  box-sizing: content-box;
}

.card {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  background-repeat: no-repeat;
  background-size: contain;
  background-position: center center;

  > div {
    position: relative;
    z-index: 1;
    width: 100%;
    height: 100%;
  }
}

#gloss1 {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  backdrop-filter: saturate(125%) contrast(175%);
  // mask-image: url(#test);
  // mask-image: url(https://mdn.github.io/shared-assets/images/examples/mask-star.svg);
  // mask-type: luminance;
  mask-mode: luminance;
  mask-repeat: no-repeat;
  mask-position: 50% 50%;
  mask-size: contain;
  // mask-size: 50px 50px;
  // mask-image:
  //   linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%, black),
  //   linear-gradient(45deg, black 25%, transparent 25%, transparent 75%, black 75%, black),
  // ;
  // mask-position: 0 0, 50px 50px;
}

#gloss2, #gloss3 {
  --color: rgba(255, 255, 255, 0.80);
  z-index: 2;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  mask-mode: luminance;
  mask-repeat: no-repeat;
  mask-position: 50% 50%;
  mask-size: contain;
  background-image: linear-gradient(to right, rgba(255, 255, 255, 0) 47%, var(--color) 48%, var(--color) 52%, rgba(255, 255, 255, 0) 53%);
  background-position: 0% 0%;
  background-repeat: no-repeat;
  background-size: 300% 100%;
  animation-name: move-x;
  animation-duration: 5s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-direction: alternate;
}
#gloss3 {
 ...

JavaScript

const setSVGMask = (elementSelector, maskSelector, invert = false, includeLuminanceToAlpha = false) => {
  const svgNS = "http://www.w3.org/2000/svg"

  const svg = document.createElementNS(svgNS, "svg")
  const g = document.createElementNS(svgNS, "g")

  svg.setAttribute("width", "200")
  svg.setAttribute("height", "200")
  svg.setAttribute("viewBox", "0 0 200 200")
  svg.setAttribute("overflow", "visible")

  if (includeLuminanceToAlpha) {
    // create a <filter><feColorMatrix type="luminanceToAlpha"/>
    const alpha = document.createElementNS(svgNS, "feColorMatrix")
    alpha.setAttribute("type", "luminanceToAlpha")

    const filter = document.createElementNS(svgNS, "filter")
    filter.setAttribute("id", "alpha")
    filter.setAttribute("filterUnits", "userSpaceOnUse");
    
    filter.append(alpha)
    svg.append(filter)

    // luminanceToAlpha disregards semi-transparent pixels,
    // so we add a black background to have no such thing
    const bg = document.createElementNS(svgNS, "rect")
    bg.setAttribute("width", "100%")
    bg.setAttribute("height", "100%")
    bg.setAttribute("fill", "#000000")
    g.append(bg)
  }

  // apply the filter on all the <mask> content
  if (invert || includeLuminanceToAlpha) {
    // g.setAttribute("filter", `${invert ? "invert() " : ""}${includeLuminanceToAlpha ? "url(#alpha)" : ""}`)
    g.setAttribute("filter", includeLuminanceToAlpha ? "url(#alpha)" : "")
  }

  const mask = document.querySelector(maskSelector).cloneNode(true)
  g.append(...mask.children)
  svg.append(g)

  const markup = new XMLSerializer().serializeToString(svg)
  let maskImage = "url('data:image/svg+xml;charset=utf8," + encodeURIComponent(markup) + "')"

  document.querySelectorAll(elementSelector).forEach((el) => {
    if (invert) {
      const compStyles = window.getComputedStyle(el);
      el.style.setProperty("mask-size", `${compStyles.getPropertyValue("mask-size")}, 100% 100%`)
     ...