Loading...
 
Skip to main content

History: CSS-Only Scroll-Triggered Animation

Source of version: 3 (current)

Copy to clipboard
{HTML()}
<!-- tutorial: https://youtu.be/onraoQExbHw -->

<style>
@layer general-styling, visuals-for-demo;

@layer general-styling {
  :root {

    /* @link https://utopia.fyi/type/calculator?c=360,18,1.2,1240,21,1.333,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
    --fs--2: clamp(0.7387rem, 0.7987rem + -0.0775vw, 0.7813rem);
    --fs--1: clamp(0.9375rem, 0.9182rem + 0.0857vw, 0.9846rem);
    --fs-0: clamp(1.125rem, 1.0483rem + 0.3409vw, 1.3125rem);
    --fs-1: clamp(1.35rem, 1.1865rem + 0.7265vw, 1.7496rem);
    --fs-2: clamp(1.62rem, 1.3287rem + 1.2948vw, 2.3322rem);
    --fs-3: clamp(1.944rem, 1.4675rem + 2.1178vw, 3.1088rem);
    --fs-4: clamp(2.3328rem, 1.5919rem + 3.2931vw, 4.144rem);
    --fs-5: clamp(2.7994rem, 1.6848rem + 4.9538vw, 5.524rem);

    /* @link https://utopia.fyi/space/calculator?c=360,18,1.2,1240,21,1.333,5,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12 */
    --space-3xs: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem);
    --space-2xs: clamp(0.5625rem, 0.5114rem + 0.2273vw, 0.6875rem);
    --space-xs: clamp(0.875rem, 0.8239rem + 0.2273vw, 1rem);
    --space-s: clamp(1.125rem, 1.0483rem + 0.3409vw, 1.3125rem);
    --space-m: clamp(1.6875rem, 1.5597rem + 0.5682vw, 2rem);
    --space-l: clamp(2.25rem, 2.0966rem + 0.6818vw, 2.625rem);
    --space-xl: clamp(3.375rem, 3.1449rem + 1.0227vw, 3.9375rem);
    --space-2xl: clamp(4.5rem, 4.1932rem + 1.3636vw, 5.25rem);
    --space-3xl: clamp(6.75rem, 6.2898rem + 2.0455vw, 7.875rem);
    --space-4xl: clamp(9rem, 8.3864rem + 2.7273vw, 10.5rem);

    /* One-up pairs */
    --space-3xs-2xs: clamp(0.3125rem, 0.1591rem + 0.6818vw, 0.6875rem);
    --space-2xs-xs: clamp(0.5625rem, 0.3835rem + 0.7955vw, 1rem);
    --space-xs-s: clamp(0.875rem, 0.696rem + 0.7955vw, 1.3125rem);
    --space-s-m: clamp(1.125rem, 0.767rem + 1.5909vw, 2rem);
    --space-m-l: clamp(1.6875rem, 1.304rem + 1.7045vw, 2.625rem);
    --space-l-xl: clamp(2.25rem, 1.5597rem + 3.0682vw, 3.9375rem);
    --space-xl-2xl: clamp(3.375rem, 2.608rem + 3.4091vw, 5.25rem);
    --space-2xl-3xl: clamp(4.5rem, 3.1193rem + 6.1364vw, 7.875rem);
    --space-3xl-4xl: clamp(6.75rem, 5.2159rem + 6.8182vw, 10.5rem);

    /* Custom pairs */
    --space-s-l: clamp(1.125rem, 0.5114rem + 2.7273vw, 2.625rem);
    --space-2xl-4xl: clamp(4.5rem, 2.0455rem + 10.9091vw, 10.5rem);
  }

  html {
    scroll-padding-block: 5lh;

    @media (prefers-reduced-motion: no-preference) {
      scroll-behavior: smooth;
    }
  }

  body {
    min-block-size: 100svb;
    font-size: var(--fs-1);
  }

  h1,
  h2,
  h3,
  h4,
  p,
  figure,
  blockquote,
  dl,
  dd {
    margin-block: 0;
  }

  h1,
  h2,
  h3,
  h4 {
    text-wrap: balance;
  }

  h1,
  h2,
  h3,
  h4,
  button,
  input,
  label {
    line-height: 1.1;
  }

  h1 {
    font-size: var(--fs-4);
  }

  img,
  picture,
  iframe {
    display: block;
    block-size: auto;
    max-inline-size: 100%;
  }

  input,
  button,
  textarea,
  select {
    font-family: inherit;
    font-size: inherit;
  }

  textarea {
    min-block-size: 5lh;
  }

  .wrapper {
    inline-size: min(100% - 2rem, 960px);
    margin-inline: auto;
  }
}

@layer demo-specific-styles {
  @layer general-demo-styling {
    ul[role="list"] {
      list-style: none;
      margin: 0;
      padding: 0;
    }

    .cluster {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      gap: var(--space-2xs);
    }

    .region {
      padding-block: var(--space-xl);
    }

    }

    .hero {
      padding-block: var(--space-xl) var(--space-l);
    }

    .hero-inner {
      display: grid;
      justify-items: start;
      gap: var(--space-s);

      h1 {
        max-inline-size: 16ch;
        letter-spacing: -0.02em;
      }
    }

    .hero-eyebrow {
      font-size: var(--fs--1);
      text-transform: uppercase;
      letter-spacing: 0.1em;
      color: var(--text-muted);
    }

    .hero-lede {
      max-inline-size: 52ch;
    }

    .hero-actions {
      margin-block-start: var(--space-2xs);
      gap: var(--space-xs);
    }

    .button {
      display: inline-block;
      font-size: var(--fs--1);
      font-weight: 600;
      text-decoration: none;
      padding: var(--space-2xs) var(--space-s);
      border: 1px solid var(--border-strong);
      border-radius: 100vmax;
      background-color: var(--surface-3);
    }

    .button-ghost {
      background-color: transparent;
      border-color: var(--border-subtle);
    }

    .section-header {
      display: grid;
      gap: var(--space-2xs);
      max-inline-size: 56ch;
      margin-block-end: var(--space-l);
    }

    .portfolio-grid {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
      gap: var(--space-m);
    }

    .portfolio-grid .card {
      display: grid;
      grid-template-rows: auto 1fr;
      block-size: 100%;
      font-size: var(--fs--1);
      background-color: var(--surface-2);
      border: 1px solid var(--border-subtle);
      border-radius: 0.75rem;
      overflow: hidden;
    }

    .portfolio-grid  .card-image {
      inline-size: 100%;
      aspect-ratio: 16 / 10;
      object-fit: cover;
      background-color: var(--surface-4);
    }

    .portfolio-grid  .card-body {
      display: grid;
      align-content: start;
      gap: var(--space-2xs);
      padding: var(--space-s);
    }

    .portfolio-grid  .card-meta {
      font-size: var(--fs--2);
      text-transform: uppercase;
      letter-spacing: 0.08em;
      color: var(--text-muted);
    }

    .portfolio-grid  .card-title {
      font-size: var(--fs-0);

      a {
        text-decoration: none;
      }
    }

    .tag-list {
      font-size: var(--fs--2);
      color: var(--text-muted);
      margin-block-start: var(--space-3xs);

      li {
        padding: 0.25em 0.75em;
        border: 1px solid var(--border-subtle);
        border-radius: 100vmax;
      }
    }

    .portfolio-grid  .card {
      animation: card-in 1s forwards paused;

      @container style(--in-view: true) {
        animation-play-state: running;
      }
    }

    @keyframes card-in {
      0% {
        opacity: 0;
        translate: 0 50px;
      }

      100% {
        opacity: 1;
        translate: 0;
      }
    }
  }
}


.portfolio-item {
  --in-view: true;
}

.portfolio-item {
  --in-view: false;
  animation: portfolio-item-in-view both;
  animation-timeline: view();
  animation-range: entry 0% entry 40%;
}

.portfolio-grid  .card {
  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      animation: card-in 1s both;
      animation-play-state: paused;
      animation-delay: calc(var(--stagger, 0s) * 0.25s);

      @container style(--in-view: true) {
        animation-play-state: running;
      }
    }
  }
}

/* for the staggered animation */
.portfolio-grid {
  --min-col-size: 17rem;
  --gap: 2rem;

  container: portfolio / inline-size;

  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(var(--min-col-size), 100%), 1fr)
  );
}

/* 2 cols + 1 gap */
@container portfolio (width > calc(17rem * 2 + 2rem)) {
  .portfolio-item:nth-child(2n) {
    --stagger: 1;
  }
}
/* three cols + 2 gaps */
@container portfolio (width > calc(17rem * 3 + 2rem * 2)) {
  .portfolio-item:nth-child(3n + 1) {
    --stagger: 0;
  }
  .portfolio-item:nth-child(3n + 2) {
    --stagger: 1;
  }
  .portfolio-item:nth-child(3n + 3) {
    --stagger: 2;
  }
}

@keyframes portfolio-item-in-view {
  from {
    --in-view: false;
  }
  to {
    --in-view: true;
  }
}

@keyframes card-reveal {
  from {
    opacity: 0;
    translate: 0 2rem;
    scale: 0.96;
  }
  to {
    opacity: 1;
    translate: 0 0;
    scale: 1;
  }
}
</style>

 <section class="hero">
    <div class="wrapper hero-inner">
      <h1>
       This is a demonstration of 
        <span class="hero-accent">scroll-triggered animation</span>
       that uses only CSS for the animation - no JavaScript required.

      </h1>
      <p class="hero-lede">
        This initial implementation is the original <a href="https://codepen.io/editor/kevinpowell/pen/01a034c8-5821-7245-9ac1-9c34526a7267">CodePen</a> demo code
pasted into a PluginHTML in a wiki page (The author's YouTube video is {HTML}[https://www.youtube.com/watch?v=onraoQExbHw&t=8|here]{HTML()}). The original code contains a lot of CSS rules that overlap Bootstrap's rules, so some CSS was removed, except for what is needed for the animation because Tiki already has the rules for layout and styling, etc. A lot of the original CSS has been removed but it can be cleaned up some more. Another step could be to replace the HTML element syntax with wiki plugins.
      </p>
      </div>
  </section>

  <section class="region" id="work">
    <div class="wrapper">
      <div class="section-header">
        <h2>More information</h2>
        <p>
          I'm not sure yet if this content, above the animated area, is just demo filler
or if it serves the purpose of keeping more cards below the bottom of the viewport
so that their animated appearance is more impressive.
        </p>
      </div>

      <ul class="portfolio-grid" role="list">
        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/tidewater/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <p class="card-meta">Client work</p>
              <h3 class="card-title"><a href="#">Tidewater Ferries</a></h3>
              <p>
                Rebuilt a seasonal booking flow that was losing people at the
                passenger details step.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>Forms</li>
                <li>Astro</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/meridian/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Meridian</a></h3>
              <p>
                A token layer and component set shared across four product teams
                and two brands.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>Tokens</li>
                <li>Docs</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/harbour/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Harbour Table</a></h3>
              <p>
                A restaurant site where the menu is editable by staff and still
                loads in under a second.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>CMS</li>
                <li>Performance</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/cadence/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Cadence</a></h3>
              <p>
                Dashboard work for a scheduling tool, focused on dense tables
                that stay readable on a phone.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>Data tables</li>
                <li>Container queries</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/fieldnotes/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Field Notes</a></h3>
              <p>
                A tiny publishing tool for people who want a blog and not a
                build pipeline.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>Node</li>
                <li>Markdown</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/lantern/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Lantern Audit</a></h3>
              <p>
                An accessibility review and remediation plan for a provincial
                government service.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>WCAG</li>
                <li>Testing</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/slate/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Slate and Salt</a></h3>
              <p>
                Storefront templates for a small ceramics studio, built to
                survive a holiday traffic spike.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>Shopify</li>
                <li>Liquid</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/novadocs/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Nova Docs</a></h3>
              <p>
                Documentation site with versioned API references and a search
                that people actually use.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>Search</li>
                <li>Information architecture</li>
              </ul>
            </div>
          </article>
        </li>

        <li class="portfolio-item">
          <article class="card">
            <img
              class="card-image"
              src="https://picsum.photos/seed/rivet/800/500"
              alt=""
              width="800"
              height="500"
              loading="lazy"
            />
            <div class="card-body">
              <h3 class="card-title"><a href="#">Rivet</a></h3>
              <p>
                A set of unstyled components I keep reaching for, finally
                written down properly.
              </p>
              <ul class="tag-list cluster" role="list">
                <li>Web components</li>
                <li>ARIA</li>
              </ul>
            </div>
          </article>
        </li>
      </ul>
    </div>
  </section>
{HTML}