Skip to content

Gallery / scroll / Scroll reveal mask

Scroll reveal mask

Items unclip themselves as they enter the scroll viewport

scroll advanced scrollrevealclip-pathview-timeline
Live preview

⚠ Browser support view() timelines need Chrome/Edge 115+; other browsers show all rows unmasked.

Techniques: animation-timeline: view() · animation-range · clip-path: inset() · @supports

Customizeupdates preview, code & clipboard
#7F77DD
260px
Code snippets/scroll/scroll-reveal-mask.html
<!--
  ============================================================
  SVGarden — https://www.svgarden.dev
  Author : Simon-Pierre Boucher
  Contact: contact@spboucher.ai
  File   : snippets/scroll/scroll-reveal-mask.html
  Desc   : Items unclip themselves as they enter the scroll viewport
  ============================================================
-->
<div class="sg-scroll-reveal-mask" style="--sg-color:#7F77DD; --sg-size:260px;">
  <p class="sg-scroll-reveal-mask-hint">scroll ↓ to reveal</p>
  <div class="sg-scroll-reveal-mask-item">
    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9" /><path d="M8 12.5 L11 15 L16 9" /></svg>
    <span class="sg-scroll-reveal-mask-bar" style="width: 72%;"></span>
  </div>
  <div class="sg-scroll-reveal-mask-item">
    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9" /><path d="M8 12.5 L11 15 L16 9" /></svg>
    <span class="sg-scroll-reveal-mask-bar" style="width: 55%;"></span>
  </div>
  <div class="sg-scroll-reveal-mask-item">
    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9" /><path d="M8 12.5 L11 15 L16 9" /></svg>
    <span class="sg-scroll-reveal-mask-bar" style="width: 80%;"></span>
  </div>
  <div class="sg-scroll-reveal-mask-item">
    <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9" /><path d="M8 12.5 L11 15 L16 9" /></svg>
    <span class="sg-scroll-reveal-mask-bar" style="width: 63%;"></span>
  </div>
</div>
<style>
  .sg-scroll-reveal-mask {
    width: 100%;
    min-width: 220px;
    height: var(--sg-size);
    overflow-y: scroll;
    border: 1px solid rgba(128, 128, 128, 0.3);
    border-radius: 10px;
    padding: 0 14px;
  }
  .sg-scroll-reveal-mask-hint {
    margin: 12px 2px calc(var(--sg-size) * 0.55);
    font-size: 13px;
    opacity: 0.6;
  }
  .sg-scroll-reveal-mask-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px;
    margin-bottom: calc(var(--sg-size) * 0.5);
    border: 1px solid rgba(128, 128, 128, 0.3);
    border-radius: 10px;
  }
  .sg-scroll-reveal-mask-item:last-of-type {
    margin-bottom: calc(var(--sg-size) * 0.35);
  }
  .sg-scroll-reveal-mask-item svg {
    width: 26px;
    height: 26px;
    flex: none;
    fill: none;
    stroke: var(--sg-color);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .sg-scroll-reveal-mask-bar {
    height: 8px;
    border-radius: 4px;
    background: var(--sg-color);
    opacity: 0.55;
  }
  @supports (animation-timeline: view()) {
    .sg-scroll-reveal-mask-item {
      animation: sg-scroll-reveal-mask-wipe linear both;
      animation-duration: auto;
      animation-timeline: view();
      animation-range: entry 0% cover 40%;
    }
  }
  @keyframes sg-scroll-reveal-mask-wipe {
    from { clip-path: inset(0 100% 0 0 round 10px); opacity: 0.2; }
    to   { clip-path: inset(0 0 0 0 round 10px); opacity: 1; }
  }
</style>

More scroll