1

I'm trying to make a certain image move from top of the document to it's bottom depending on the scroll percentage, for example, when you load the site, the image will be on top of the page and as the user scrolls down it'll go down little by little depending on the overall document percentage, until at 100% it's at the bottom.
I've went through lots of similar solutions on stackoverflow and other sites, but only two seemed close to being what I need.
The first works but only on one resolution which is manually adjusted in the code:
var imgscroll = document.getElementById('myimg');

window.addEventListener('scroll', function() {
  var scrollposition = window.scrollY;
  imgscroll.style.top = scrollposition * 0.1323 + 'vh';
}

The second is from a stackoverflow answer - located here and copied below - I think the percentage part is what I need, but couldn't make it work (the image stopped moving):

var container = document.getElementById('container');
var windowHeight = window.innerHeight;
var windowWidth = window.innerWidth;
var scrollArea = 1000 - windowHeight;
var square1 = document.getElementsByClassName('square')[0];
var square2 = document.getElementsByClassName('square')[1];

// update position of square 1 and square 2 when scroll event fires.
window.addEventListener('scroll', function() {
  var scrollTop = window.pageYOffset || window.scrollTop;
  var scrollPercent = scrollTop/scrollArea || 0;

  square1.style.left = scrollPercent*window.innerWidth + 'px';
  square2.style.left = 800 - scrollPercent*window.innerWidth*0.6 + 'px';
});

I'd appreciate any help or tips on how to reach the answer.

SA.93
  • 141
  • 14
  • It's called "parallax scrolling" – Dai Oct 19 '21 at 21:08
  • @Dai I tried parallax scrolling while searching for a solution, unfortunately it wasn't what I needed, the image needs to be visible at all times and move down a bit with every scroll over the content, no other changes to the image. – SA.93 Oct 19 '21 at 21:16
  • What should happen if the viewport is smaller than the image though? – Dai Oct 19 '21 at 21:19
  • 1
    Performance tip: don't use a blocking `scroll` event-listener, instead use `passive` events: https://stackoverflow.com/questions/46542428/warning-added-non-passive-event-listener-to-a-scroll-blocking-touchstart-even, and avoid setting `position` style properties. Instead use `transform: translateY(...)` - that way the effect will be very smooth because it won't cause retroactive DOM updates (which block the render loop). – Dai Oct 19 '21 at 21:20
  • The image width is restricted in `vw` units so it adjusts (also tested this resizing on other resolutions and the image did adjust). I'm still new to this, so thanks for the tips, I'll make sure to read about the transform method! – SA.93 Oct 19 '21 at 21:23

1 Answers1

1

Personally I find the approach where you control the position of the image by using animation-play-state: paused and assigning a CSS variable to the animation-delay one of the neatest bit of scripts I ever saw on the web. Here's the pen by Chris Coyier it's based on. And a quote from his website that describes the mechanism:

A positive animation delay is where the animation waits a certain amount of time to begin. A negative animation delay starts the animation immediately, as if that amount of time has already gone by. In other words, start the animation at a state further into the animation cycle.

When the window has loaded, we first calculate the available space below the image and the amount of page overflow. The first CSS variable --maximum defines the end point of the animation. This is recalculated when the user resizes the screen. Then when scrolling, the ratio of progress is set through another CSS variable --epoch that controls the timing of the keyframe animation.

let aim = document.getElementById('image'), room, overflow;

window.addEventListener('load', setEdge);
window.addEventListener('resize', setEdge);

window.addEventListener('scroll', function() {

  let ratio = (this.pageYOffset || this.scrollY)/overflow;

  aim.style.setProperty('--epoch', ratio);
});

function setEdge() {

  room = window.innerHeight;
  overflow = document.body.scrollHeight-room;

  aim.style.setProperty('--maximum', room-aim.height + 'px');
}
body {
  margin: 0;
  height: 700vh;
}

#image {
position: fixed;
animation-duration: 1s;
animation-timing-function: linear;
animation-play-state: paused;
animation-iteration-count: 1;
animation-fill-mode: both;
animation-name: move;
animation-delay: calc(var(--epoch) * -1s);
}

@-webkit-keyframes move {

0% {
 transform: translateY(0);
}
100% {
 transform: translateY(var(--maximum));
}
}

@keyframes move {

0% {
 transform: translateY(0);
}
100% {
 transform: translateY(var(--maximum));
}
}
<img id="image" src="https://via.placeholder.com/140x100" alt="">

For those that want to play around with it: https://jsfiddle.net/z2r40y8c/

Shikkediel
  • 5,195
  • 16
  • 45
  • 77
  • Thanks, great method, learned alot of things I wasn't familiar with! Can you please explain why with `animation-delay` you multiplied by -1 seconds? – SA.93 Oct 20 '21 at 14:50
  • 1
    That's just one way to make the number negative (as per the mechanism described in the quote) and adhere to the correct measurement unit. I took it from the original example. You could also use `animation-delay: var(--epoch)` in the CSS and then set the value with JS through `aim.style.setProperty('--epoch', -ratio + 's')`. Same result. – Shikkediel Oct 20 '21 at 23:11