<script type="module" src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.1.1/model-viewer.min.js"></script>

<header>
  Demo for <a href="https://brm.us/scroll-driven-3d" target="_top">https://brm.us/scroll-driven-3d</a>
</header>

<model-viewer
  alt="Nike Air Max 90"
  src="https://assets.codepen.io/89905/nike_air_max_90.glb"
></model-viewer>

<footer>
  <p>3D Model <a href="https://skfb.ly/ou8Yp" target="_top">“Nike Air Max 90”</a> by StudioAira! – <a href="http://creativecommons.org/licenses/by-nc/4.0/" target="_top">CC BY-NC 4.0</a>.</p>
</footer>

<div class="warning">
  <p>⚠️ Your browser does not support Scroll-driven Animations. Please use Chrome 115 or newer.</p>
</div>
@keyframes foo {
  to {
    scale: 1;
  }
}

model-viewer {
  display: block;
  width: 100%;
  height: 95vh;
  position: fixed;
  animation: foo linear both;
  animation-timeline: scroll(block root);
}

model-viewer::part(default-progress-bar) {
  display: none;
}

header,
footer {
  position: fixed;
  left: 0;
  right: 0;
  text-align: center;
  font-style: italic;
}
header {
  top: 1em;
}
footer {
  bottom: 0;
}

html {
  height: 400vh;
}

body {
  font-family: helvetica;
}

@layer warning {
  .warning {
    box-sizing: border-box;
    padding: 1em;
    margin: 1em 0;
    border: 1px solid #ccc;
    background: rgba(255 255 205 / 0.8);

    position: fixed;
    top: 40vh;
    font-size: 2em;
    left: 1em;
    right: 1em;
    max-width: 80ch;
    margin: 0 auto;
    z-index: 1000;
  }

  .warning > :first-child {
    margin-top: 0;
  }

  .warning > :last-child {
    margin-bottom: 0;
  }

  .warning a {
    color: blue;
  }
  .warning--info {
    border: 1px solid #123456;
    background: rgb(205 230 255 / 0.8);
  }
  .warning--alarm {
    border: 1px solid red;
    background: #ff000010;
  }

  @supports (animation-timeline: view()) {
    .warning:not([data-bug]) {
      display: none;
    }
  }

  @supports (animation-range: 0vh 90vh) {
    .warning[data-bug="1427062"] {
      display: none;
    }
  }
}
const trackAnimationProgress = (animation, cb, precision = 5) => {
  let progress = 0;
  const updateValue = () => {
    if (animation && animation.currentTime) {
      let newProgress = animation.effect.getComputedTiming().progress * 1;
      if (animation.playState === "finished") newProgress = 1;
      newProgress = Math.max(0.0, Math.min(1.0, newProgress)).toFixed(precision);

      if (newProgress != progress) {
        progress = newProgress;
        cb(progress);
      }
    }
    requestAnimationFrame(updateValue);
  };
  requestAnimationFrame(updateValue);
}

const model = document.querySelector("model-viewer");
trackAnimationProgress(model.getAnimations()[0], (progress) => {
  model.orientation = `0deg 0deg ${progress * -360}deg`;
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.