0

I have a list of images and want to move the images slowly up/down while scrolling down/up.

The images itself are set by "object-fit: cover;" and "object-position: center 50%;", so I have space to move the image down to the maximum of "object-position: center 100%;" while scrolling up. And the opposite, to move the image up to the minimum of "object-position: center 0;" while scrolling down.

For short: When user scrolls the list of images down, then each image should slowly show more from its hidden top and vice versa.

I find this nice parralax-effect on whatsapp client (ios). When user is scrolling down, the media content is scrolling a little up and vice versa. I want to achieve exactly the same.

So I made the following code - it works, however, performance is bad and I look for a better solution. Does anyone know a better solution for this problem? Maybe by using "pure css parralax" (http://keithclark.co.uk/articles/pure-css-parallax-websites/). This would be the best solution - however, I cannot adopt this technique to my problem.

You can copy/paste it to a new index.html and it will work as it is.

Here is the full working code (with bad performance):

(function() {
     document.addEventListener('DOMContentLoaded', function() {
    
      /** returns true, if node is in viewport.
      */
function node_is_in_viewport(element) {
    
         var rect = element.getBoundingClientRect();
         var html = document.documentElement;
         
         return (
           rect.top >= 0 &&
           rect.left >= 0 &&
           rect.bottom <= (window.innerHeight || html.clientHeight) &&
           rect.right <= (window.innerWidth || html.clientWidth)
         );
      }
    
      /** returns "y-value" of css-property "object-position"
      */
      function getObjectPosY(translateString){
       var n = translateString.indexOf('%');
       var n1 = translateString.lastIndexOf("%");
    
       var res = parseInt(translateString.slice(n+1,n1));
       return res;
      }
    
      var list_img = document.querySelector('.list-img');
      var pos_list = list_img.scrollTop;
      var all_imgs = document.querySelectorAll('.list-img>li img');
      
      console.log('actual position of the list:');
      console.log(pos_list);
     
      list_img.addEventListener('scroll',  function(e){
    
        var scroll = this.scrollTop;
      
        // positive number means "scroll down"
        // negative number means "scroll up"
        var offsetScrollTreshold = scroll - pos_list; // unused
    
        // move image 1 unit up/down
        var offsetScroll = 3;
        var speed = 1;
        
        console.log('position of list (pos_list):');
        console.log(pos_list);
        
        console.log('scroll:');
        console.log(scroll);
        
        console.log('offset (scroll - pos_list):');
        console.log(offsetScrollTreshold);
    
        if(scroll > pos_list) {
          console.log('User is scrolling DOWN');
          console.log('I want to show more from the hidden top of all the images within the actual viewport while user is scrolling down. But each image should not exceed its original image size.');
    
          list_img.classList.add('scroll-down');
          list_img.classList.remove('scroll-up');
    
             [].forEach.call(all_imgs, function(node) {
              
              var node_in_view = node_is_in_viewport(node);
    
              // if image is in viewport move it up to "make a parallax effect"
              if(node_in_view){
               console.log('-----');
               console.log('actual img to move up (new Y must be lower):');
               console.log(node.src);
    
               var old_y = 0;
               
               var theCSSprop = window.getComputedStyle(node, null).getPropertyValue('object-position');
               if( theCSSprop.indexOf('%')!=-1 ){
                console.log(theCSSprop);
    
                // returns the actual "y"-position of the image
                old_y = getObjectPosY(theCSSprop);
                console.log('old_y:');
                console.log(old_y);
               }
               
               
               // User is scrolling down, so I want to view more from the "top of the image" which is actually a little hidden (because of object-fit: center 50%)
               // to view more from the top of the image, I have to set the y-value (actually: 50%) lower, but not more than 0.
               if(old_y > 0 && old_y <= 100){
            
            console.log('scroll image "slowly" up while user is scrolling down (parallax effect)');
                var new_y = old_y - offsetScroll * speed;
    
                if(new_y<0){
                 new_y = 0;
            }
                
                console.log('new_y:');
                console.log(new_y);
                node.style.objectPosition = '50% '+ new_y + '%';
               }
              }
     
          });
            }
            else {
    
             console.log('User is scrolling UP');
         console.log('I want to show more from the hidden bottom of all the images within the actual viewport while user is scrolling up. But each image should not exceed its original image size.');
    
         list_img.classList.remove('scroll-down');
         list_img.classList.add('scroll-up');
             
         [].forEach.call(all_imgs, function(node) {
              
          var node_in_view = node_is_in_viewport(node);
    
          // if image is in viewport move it down to "make a parallax effect"
              if(node_in_view){
    
               console.log('actual img to move down (new Y must be higher):');
               console.log(node.src);
               
               var theCSSprop = window.getComputedStyle(node, null).getPropertyValue('object-position');
               if(theCSSprop.indexOf('%')!=-1){
                console.log(theCSSprop);
    
                // returns the actual "y"-position of the image
                old_y = getObjectPosY(theCSSprop);
                
                console.log('old_y:');
                console.log(old_y);
               }
    
               // User is scrolling up, so I want to view more from the "bottom of the image" which is actually a little hidden (because of object-fit: center 50%)
               // to view more from the bottom of the image, I have to set the y-value (actually: 50%) higher, but not more than 100%.
               if(old_y >= 0 && old_y < 100){
             
                console.log('scroll image "slowly" down while user is scrolling up (parallax effect)');
                
                var new_y = old_y + offsetScroll * speed;
    
                if(new_y>100){
                 new_y = 100;
            }
                
                console.log('new_y:');
                console.log(new_y);
    
                node.style.objectPosition = '50% '+ new_y + '%';
               }
              }
         });
        }
        
       pos_list = scroll;
       console.log('pos_list:');
       console.log(pos_list);
       
      });/*end eventlistener scroll */
      
     }); /*end .DOMContentLoaded */
    }());
    
    
   
    
    body, html, #ctn_infinity, .list-img{
     height: 100%;
     width: 100%;
     margin: 0;
     padding: 0;
    }
    
    .list-img{
     height: 90vh;
     overflow: auto;
    }
    
    
    .list-img{
     list-style: none;
     width: 400px;
    }
    
    .list-img>li{
     display: flex;
     flex-direction: column;
     border: 2px solid black;
     margin: 16px auto;
    }
    
    .list-img div{
     background: yellow;
    }
    
    .list-img img{
     object-fit: cover;
     width: 100%;
     /* original image size: 400 width x 200 height */
     /* I use object position center: when scrolling up or down, images should "slowly" show its hidden part of its images (max. 25px top or 25px down)  */
     height: 150px;
     /* Unfortunately, I cannot use "transform: translateY(-5px);", because this will move the whole img-container instead of the img itself up/down" */
     object-position: center 50%;
    }
    
    
 
    
<!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>HTML-TEST</title>
  
    
    </head>
    <body>
     <div id="ctn_infinity">
      <ul class="list-img">
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
       <li><img src="http://lorempixel.com/400/200">
        <div>lorem epsum..</div></li>
      </ul>
     </div>
    </body>
    </html>
nimo23
  • 5,170
  • 10
  • 46
  • 75

1 Answers1

0

I can't find object-position on csstriggers.com. But I checked it myself, and as soon as I change the object position of an element with object-fit: cover, I get a Recalculate style and a Paint.

In case you aren't familiar with these, these are ways your browser updates your screen after specific events. And the events your code trigger are very heavy. Now on every scroll event your code triggers expensive paint events on the browsers window.

Basically what you want to do is refactor it so that your code doesn't have to be repaint. You will probably want to change the objectPosition to a .style.transorm = "translate3d(0, " + new_y + ", 0)";. Those shouldn't trigger the paint events as it does now.

Thing with this though, that there could be ways it will still trigger paints. I recommend you reading about scrolling performance since it's too much to explain on stack overflow, Paul Lewis wrote a nice article about this.

Zonakusu
  • 18
  • 4
  • My first solution was based on translate3d (translateY). However, I cannot use "translate3D" because, when using, then the whole image will move instead of its content. – nimo23 Feb 15 '17 at 13:19
  • The best solution would be really a css-based-technique such as http://keithclark.co.uk/articles/pure-css-parallax-websites/. But I cannot adopt this kind of solution to this problem. Maybe I am missing some points. – nimo23 Feb 15 '17 at 14:02