仓库源文站点原文

TOC

Rainbow Artword

<img alt="Rainbow Artword" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/008vxvgGly1h8t01qct5yj308q05ct8r.jpg" width="150">

<style>
  .wordart {
    display: inline-block;
    background: linear-gradient(
      90deg,
      #ff0000,
      #ff8800,
      #ffff00,
      #02be02,
      #0000ff,
      #4f00ff,
      #9c00ff
    );
    background-clip: text;
    -webkit-background-clip: text;
    color: transparent;
    font-size: 60px;
    font-weight: bold;
    transform: skewY(-8deg) scaleY(1.3) scaleX(0.8);
    filter: drop-shadow(2px 2px 0px rgba(0, 0, 0, 0.2));
  }
</style>

<span class="wordart">WordArt</span>

Hover Text Effects

<img alt="Hover Text Effects" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/008vxvgGly1h8t04ox2d3j30e2048jrb.jpg" width="200">

https://codepen.io/jh3y/pen/abGPKGO

<script src="https://unpkg.com/splitting/dist/splitting.min.js"></script>
<style>
  .char {
    --pop: 0;
    display: inline-block;
    position: relative;
    color: transparent;
    z-index: calc(1 + (var(--pop) * 2));
  }

  .char:after {
    content: attr(data-char);
    position: absolute;
    inset: 0;
    color: hsl(45 calc(var(--pop) * 100%) calc(80% - (30% * var(--pop))));
    translate: 0 calc(var(--pop, 0) * -65%);
    scale: calc(1 + var(--pop) * 0.75);
    transition: translate 0.2s, scale 0.2s, color 0.2s;
  }

  .char:hover {
    --pop: 1;
  }

  /* elements that are immediately before and after the char being hovered */
  .char:hover + .char,
  .char:has(+ .char:hover) {
    --pop: 0.4;
  }
</style>

<h1 data-splitting>Happy Birthday!</h1>
<script> Splitting(); </script>

Apple-style OS dock

CSS only, no JS. This one would be pretty sweet as a nav on your portfolio.

<img alt="apple-style-dock" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/dwucuh.png" width="450">

https://codepen.io/jh3y/pen/GRwwWoV

.b:has(+ .b:hover),
.b:hover + .b {
  flex: calc(0.2 + (sin(30deg) * 1.5));
  translate: 0 calc(sin(30deg) * -75%);
}

3D Flip Hover Effects

<img alt="3D Flip Hover" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/Screen%20Shot%202023-02-18%20at%207.06.16%20PM.png" width="200">

https://codepen.io/rikanutyy/pen/PEJBxX

<style>
  .card {
    color: #013243;
    position: absolute;
    top: 50%;
    left: 50%;
    width: 300px;
    height: 400px;
    background: #e0e1dc;
    transform-style: preserve-3d;
    transform: translate(-50%,-50%) perspective(2000px);
    box-shadow: inset 300px 0 50px rgba(0,0,0,.5), 20px 0 60px rgba(0,0,0,.5);
    transition: 1s;
  }

  .card:hover {
    transform: translate(-50%,-50%) perspective(2000px) rotate(15deg) scale(1.2);
    box-shadow: inset 20px 0 50px rgba(0,0,0,.5), 0 10px 100px rgba(0,0,0,.5);
  }

  .card:before {
    content:'';
    position: absolute;
    top: -5px;
    left: 0;
    width: 100%;
    height: 5px;
    background: #BAC1BA;
    transform-origin: bottom;
    transform: skewX(-45deg);
  }

  .card:after {
    content: '';
    position: absolute;
    top: 0;
    right: -5px;
    width: 5px;
    height: 100%;
    background: #92A29C;
    transform-origin: left;
    transform: skewY(-45deg);
  }

  .card .imgBox {
    width: 100%;
    height: 100%;
    position: relative;
    transform-origin: left;
    transition: .7s;
  }

  .card .bark {
    position: absolute;
    background: #e0e1dc;
    width: 100%;
    height: 100%;
    opacity: 0;
    transition: .7s;
  }

  .card .imgBox img {
    min-width: 250px;
    max-height: 400px;
  }

  .card:hover .imgBox {
    transform: rotateY(-135deg);
  }

  .card:hover .bark {
    opacity: 1;
    transition: .6s;
    box-shadow: 300px 200px 100px rgba(0, 0, 0, .4) inset;
  }

  .card .details {
    position: absolute;
    top: 0;
    left: 0;
    box-sizing: border-box;
    padding: 0 0 0 20px;
    z-index: -1;
    margin-top: 70px;
  }
</style>

<div class="card">
  <div class="imgBox">
    <div class="bark"></div>
    <img src="https://placekitten.com/300/400">
  </div>
  <div class="details">
    <h4>HAPPY BIRTHDAY</h4>
  </div>
</div>

Color Palettes

<img alt="okLCH Color Palettes" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/008vOhrAly1he4xakrh5qj30tg0ka0ts.jpg" width="500">

<style>
  html {
    --hue: 140;

    --swatch-1: oklch(99% .05 var(--hue));
    --swatch-2: oklch(90% .1 var(--hue));
    --swatch-3: oklch(80% .2 var(--hue));
    --swatch-4: oklch(72% .25 var(--hue));
    --swatch-5: oklch(67% .31 var(--hue));
    --swatch-6: oklch(50% .27 var(--hue));
    --swatch-7: oklch(35% .25 var(--hue));
    --swatch-8: oklch(25% .2 var(--hue));
    --swatch-9: oklch(13% .2 var(--hue));
    --swatch-10: oklch(5% .1 var(--hue));

    --text-1: var(--swatch-10);
    --text-2: var(--swatch-9);
    --surface-1: var(--swatch-1);
    --surface-2: var(--swatch-2);
    --surface-3: var(--swatch-3);
  }

  html {
    background: var(--surface-1);
    color: var(--text-1);
  }
  body {
    display: grid;
    /* justify-content && align-content */
    place-content: center;
    gap: 5vmin;
    grid-auto-flow: column;
  }
  .palette {
    display: grid;
    grid-auto-rows: 8vh;
    grid-template-columns: 20vw;
  }
  .swatch {
    box-shadow: inset 0 0 0 1px oklch(50% 0 0 / 20%);
  }
  .swatch:nth-of-type(1)  { background: var(--swatch-1) }
  .swatch:nth-of-type(2)  { background: var(--swatch-2) }
  .swatch:nth-of-type(3)  { background: var(--swatch-3) }
  .swatch:nth-of-type(4)  { background: var(--swatch-4) }
  .swatch:nth-of-type(5)  { background: var(--swatch-5) }
  .swatch:nth-of-type(6)  { background: var(--swatch-6) }
  .swatch:nth-of-type(7)  { background: var(--swatch-7) }
  .swatch:nth-of-type(8)  { background: var(--swatch-8) }
  .swatch:nth-of-type(9)  { background: var(--swatch-9) }
  .swatch:nth-of-type(10) { background: var(--swatch-10) }

  .card {
    display: grid;
    border-radius: 10px;
    background: var(--surface-2);
    border: 1px solid var(--surface-3);
    padding: 1rem;
  }
</style>

<body>
  <div class="palette">
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
    <div class="swatch"></div>
  </div>

  <article>
    <div class="card">
      <h2>I'm a card</h2>
      <p>Lorem ipsum dolor, sit amet consectetur adipisicing elit. Itaque doloremque modi veniam aspernatur voluptatum
        labore dolores perspiciatis.</p>
    </div>
  </article>
</body>

Another way is using CSS color-mix(), which is stable in Chrome 111. The trick for creating semi-opaque versions of the brand colors is mixing them with the transparent color value.

:root {
  --brandBlue: skyblue;
  --brandBlue-a10: color-mix(in srgb, var(--brandBlue), transparent 90%);
  --brandBlue-a20: color-mix(in srgb, var(--brandBlue), transparent 80%);
  --brandBlue-a30: color-mix(in srgb, var(--brandBlue), transparent 70%);
  --brandBlue-a40: color-mix(in srgb, var(--brandBlue), transparent 60%);
  --brandBlue-a50: color-mix(in srgb, var(--brandBlue), transparent 50%);
  --brandBlue-a60: color-mix(in srgb, var(--brandBlue), transparent 40%);
  --brandBlue-a70: color-mix(in srgb, var(--brandBlue), transparent 30%);
  --brandBlue-a80: color-mix(in srgb, var(--brandBlue), transparent 20%);
  --brandBlue-a90: color-mix(in srgb, var(--brandBlue), transparent 10%);
}

3D Clock

<img alt="3D Clock" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/3dclock.jpg" width="300">

https://codepen.io/bigxixi/pen/abjEMbg

Animation with View Transitions

When a view transition occurs between two different documents it is called a cross-document view transition. This is typically the case in multi-page applications (MPA). Chrome 126 enables Cross-Document View Transitions triggered by a same-origin navigation. From now on, you no longer need rearchitect your app to an SPA to use View Transitions.

Animation CSS grid alignments: https://codepen.io/argyleink/pen/NWOEvro

<style>
  body {
    display: grid;
    place-content: center;
  } 
  .box {
    /* Having a name means "hold onto this element and try to tween it" (otherwise you get the cross-fade) */
    view-transition-name: box; /* whatever a unique name */
    width: 100px;
    height: 100px;
    background: blue;
  }
</style>

<div class="box"></div>
<script>
  const positions = ['start', 'end', 'center']

  function getRandomInt(max) {
    return Math.floor(Math.random() * max);
  }

  function setRandomAlignments() {
    document.body.style.alignContent = positions[getRandomInt(3)]
    document.body.style.justifyContent = positions[getRandomInt(3)]
  }

  document.body.addEventListener('click', e => {
    if (!document.startViewTransition)
      setRandomAlignments()
    else
      document.startViewTransition(() => {
        setRandomAlignments()
      })
  })
</script>

Tag selection: https://codepen.io/dannymoerkerke/pen/VYZxYdy

<div class="search"></div>
<div class="tags">
  <button>Docker<span>X</span></button>
  <button>Kubernetes<span>X</span></button>
  <button>AWS<span>X</span></button>
</div>

<script>
  const tags = document.querySelectorAll('button');
  const search = document.querySelector('.search');

  tags.forEach((tag, index) => {
    tag.style.viewTransitionName = `tag-${index}`;
    tag.style.order = index;
  });

  const tagsContainer = document.querySelector('.tags');

  tagsContainer.addEventListener('click', (e) => {
    const tag = e.target.closest('button');
    if (tag) {
      document.startViewTransition(() => {
        search.appendChild(tag);
      });
    }
  });

  search.addEventListener('click', (e) => {
    const span = e.target.closest('span');
    if (span) {
      const tag = span.closest('button');
      document.startViewTransition(() => {
        tagsContainer.appendChild(tag);
      });
    }
  });
</script>

https://codepen.io/argyleink/pen/GRPRJyM

<img alt="just the tabs" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/245b6eaa-8117-430d-a2aa-faa40e0e51a2.png" width="450">

Filter and backdrop filter

backdrop-filter has the same effect as filter, with one notable difference — backdrop filters apply only to areas behind the element instead of to the element and its children. Filters, on the other hand, apply directly to the element and its children, and don’t affect anything behind the element.

<figure> <img alt="filter" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/a9f0c5e0-6f17-4068-b481-17bfac204791.png" width="500"> <figcaption>filter example</figcaption> </figure><figure> <img alt="backdrop-filter" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/t0635k.png" width="500"> <figcaption>backdrop-filter example</figcaption> </figure>
<div class="parent">
  <div class="blur">Blur</div>
  <div class="invert">Invert</div>
  <div class="hue">Hue</div>
  <div class="grayscale">Grayscale</div>
</div>
<style>
.parent {
  background-image: url("/images/neue-donau.webp");
}
.blur {
  backdrop-filter: blur(5px);
}
.invert {
  backdrop-filter: invert(1);
}
.hue {
  backdrop-filter: hue-rotate(260deg);
}
.grayscale {
  backdrop-filter: grayscale(100%);
}
</style>

Scroll-driven animations

At its simplest, the animation-timeline property lets us link any keyframe animation to the progress of scroll. They still run from 0-100%. But now, 0% is the scroll start position and 100% is the scroll end position.

@keyframes spin {
  to {
    transform: rotateY(5turn);
  }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    div {
      animation: spin linear both;
      animation-timeline: scroll();
    } 
  }
}

Next, change scroll() to view(), which means we can trigger animations when elements enter and exit the viewport. This time 0% is when the element is entering the scroll area and 100% is when it’s about to go out of that scroll area.

/* Animate images: https://codepen.io/una/pen/KKYZzJM */
@keyframes appear {
  from { opacity: 0; scale: 0.8; }
  to { opacity: 1; scale: 1; }
}

img {
  animation: appear linear both;
  animation-timeline: view();
  animation-range: entry 25% cover 50%;
}

Because scroll-driven animations are only active when there is scrollable overflow, it is possible to use them as a mechanism to detect if an element can scroll or not.

.container {
  height: 250px;
  width: 250px;
  overflow-y: auto;

  --can-scroll: 0;
  animation: detect-scroll;
  animation-timeline: scroll(self);
}

@keyframes detect-scroll {
  from, to  {
    --can-scroll: 1;
  }
}

"Unleash the Power of Scroll-Driven Animations" from Google is a 10-part video course to learn all about scroll-driven animations.

Reveal hover effect

https://codepen.io/t_afif/pen/GRYEZrr

<img src="https://picsum.photos/seed/picsum/200/200" class="left">
<img src="https://picsum.photos/seed/picsum/200/200" class="right">

<style>
img {
  --s: 200px; /* the image size */

  width: var(--s);
  height: var(--s);
  box-sizing: border-box;
  object-fit: cover;
  transition: .5s;
}
img.left {
  object-position: right;
  padding-left: var(--s);
  background: #542437;
}
img.right {
  object-position: left;
  padding-right: var(--s);
  background: #8A9B0F;
}

img:hover {
  padding: 0;
}
</style>

Gradient border card

<img alt="Gradient Border" src="https://raw.gitmirror.com/kexiZeroing/blog-images/main/gradient-border.png" width="100">

The two layers stack on top of each other:

.box {
  width: 100px;
  height: 100px;
  border: solid 4px #0000;
  border-radius: 16px;
  /* `0 0` means no transition resulting in a solid fill of the specified color. */
  background: conic-gradient(rgb(0 0 0) 0 0) padding-box,
    linear-gradient(45deg, #ffbc00, #ff0058) border-box;
}

/* conic-gradient(
  rgb(0 0 0 / .75) 0deg,
  rgba(255, 255, 255, 0.5) 90deg,
  rgba(255, 0, 0, 0.75) 180deg
); */

The Periodic Table

https://dev.to/madsstoumann/the-periodic-table-in-css-3lmm

ol {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(18, 1fr);
  grid-template-rows: repeat(10, 1fr);
}
li {
  &:nth-of-type(2) { grid-column: 18; } /* pushed to the last column */
}

/* filter */
body:has(#alk:checked) li:not(.alk) { 
  opacity: 0.2;
}

Double input range slider

https://codepen.io/alexpg96/pen/xxrBgbP

<style>
  .container {
    position: relative;
    width: 300px;
    height: 100px;
  }
  .slider-track {
    width: 100%;
    height: 5px;
    position: absolute;
    margin: auto;
    top: 0;
    bottom: 0;
  }
  input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    outline: none;
    position: absolute;
    margin: auto;
    top: 0;
    bottom: 0;
    background-color: transparent;
    pointer-events: none;
  }
  input[type="range"]::-webkit-slider-runnable-track {
    -webkit-appearance: none;
    height: 5px;
  }
  input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    height: 22px;
    width: 22px;
    background-color: blue;
    cursor: pointer;
    margin-top: -8px;
    pointer-events: auto;
    border-radius: 50%;
  }
  input[type="range"]:active::-webkit-slider-thumb {
    background-color: #ffffff;
    border: 1px solid blue;
  }
</style>

<body>
  <div class="values">
    <span id="range1">0</span>
    <span> &dash; </span>
    <span id="range2">100</span>
  </div>
  <div class="container">
    <div class="slider-track"></div>
    <input type="range" min="0" max="100" value="30" id="slider-1" oninput="slideOne()">
    <input type="range" min="0" max="100" value="70" id="slider-2" oninput="slideTwo()">
  </div>

  <script>
    window.onload = function () {
      slideOne();
      slideTwo();
    };

    let sliderOne = document.getElementById("slider-1");
    let sliderTwo = document.getElementById("slider-2");
    let displayValOne = document.getElementById("range1");
    let displayValTwo = document.getElementById("range2");
    let sliderTrack = document.querySelector(".slider-track");
    let sliderMaxValue = 100;

    function slideOne() {
      if (parseInt(sliderTwo.value) <= parseInt(sliderOne.value)) {
        sliderOne.value = parseInt(sliderTwo.value);
      }
      displayValOne.textContent = sliderOne.value;
      fillColor();
    }
    function slideTwo() {
      if (parseInt(sliderTwo.value) <= parseInt(sliderOne.value)) {
        sliderTwo.value = parseInt(sliderOne.value);
      }
      displayValTwo.textContent = sliderTwo.value;
      fillColor();
    }
    function fillColor() {
      percent1 = (sliderOne.value / sliderMaxValue) * 100;
      percent2 = (sliderTwo.value / sliderMaxValue) * 100;
      // The color gray starts from the beginning and transitions to percent1%
      // At percent1%, the color changes to blue, and the color blue continues up to percent2%
      // At percent2%, the color changes back to gray
      sliderTrack.style.background = `linear-gradient(to right, lightgray ${percent1}%, blue ${percent1}%, blue ${percent2}%, lightgray ${percent2}%)`;
    }
  </script>
</body>
</html>