Search code examples
javascriptfor-loopdry

How to condense JavaScript Using Loops


I have the following code working properly to responsively lazy load background images for a number of divs on a page:

// get frames
// REFACTOR LIST:
var frame1 = document.getElementById('frame1');
var frame2 = document.getElementById('frame2');
var frame3 = document.getElementById('frame3');
var frame4 = document.getElementById('frame4');
var frame5 = document.getElementById('frame5');

// create Lazy loader
var myLazyLoad = new LazyLoad({
  elements_selector: ".lazy"
});

// load images responsively
function loadImgs() {
  
    console.log('Loading images...');
  
    if(window.matchMedia("only screen and (max-width:700px)").matches) {
    // viewport is less than or equal to 700 pixels wide
    // REFACTOR LIST:
    var src1 = frame1.getAttribute('data-src-small');
    var src2 = frame2.getAttribute('data-src-small');
    var src3 = frame3.getAttribute('data-src-small');
    var src4 = frame4.getAttribute('data-src-small');
    var src5 = frame5.getAttribute('data-src-small');
  } else {
    // viewport is greater than 700 pixels wide
    // REFACTOR LIST:
    var src1 = frame1.getAttribute('data-src-large');
    var src2 = frame2.getAttribute('data-src-large');
    var src3 = frame3.getAttribute('data-src-large');
    var src4 = frame4.getAttribute('data-src-large');
    var src5 = frame5.getAttribute('data-src-large');
  } 

  // set data-src for lazy loader
  // REFACTOR LIST:
  frame1.setAttribute('data-src', src1);
  frame2.setAttribute('data-src', src2);
  frame3.setAttribute('data-src', src3);
  frame4.setAttribute('data-src', src4);
  frame5.setAttribute('data-src', src5);
    
  // tell lazy loader that the data should be re-processed
  // REFACTOR LIST:
  frame1.removeAttribute('data-was-processed');
  frame2.removeAttribute('data-was-processed');
  frame3.removeAttribute('data-was-processed');
  frame4.removeAttribute('data-was-processed');
  frame5.removeAttribute('data-was-processed'); 

  // tell lazy loader to update
  myLazyLoad.update();
}

// load images initially
loadImgs();

// reload images when window is resized across the 700px breakpoint
var lastWindowSize = window.innerWidth;
window.onresize = function(event) {
    var currentWindowSize = window.innerWidth; 
    if((lastWindowSize <= 700 && currentWindowSize > 700) || (lastWindowSize > 700 && currentWindowSize <= 700)) {
    loadImgs();
  }
  lastWindowSize = currentWindowSize;
};
html {
  box-sizing: border-box;
}

*, *::before, *::after {
  box-sizing: inherit;
  &:focus {
    outline: none;
  }
}

* {
  font-family: monaco, courier;
}

body {
  margin: 0;
  padding: 0;
}

.wrapper {
  width: 100%;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center ;
  background: #ddd;
}

p {
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  padding: 8px;
  color: darkslategray;
  background: gold;
}

.frame {
  width: 80vw;
  height: 200px;
  margin: 0 0 1rem 0;
  padding: 0;
  position: relative;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  border: 2px solid gold;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-lazyload/8.7.1/lazyload.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

<!-- https://stackoverflow.com/questions/50431531/lazylaoding-css-background-not-html-img-tags -->

<main class="wrapper">

  <a href="#">
    <div id="frame1" class="frame lazy" 
      data-src-small="https://source.unsplash.com/random/400x200?sig=1"
      data-src-large="https://source.unsplash.com/random/1200x600?sig=1">
      <p>1</p>
    </div>
  </a>

  <a href="#">
    <div id="frame2" class="frame lazy" 
      data-src-small="https://source.unsplash.com/random/400x200?sig=2"
      data-src-large="https://source.unsplash.com/random/1200x600?sig=2">
      <p>2</p>
    </div>
  </a>

  <a href="#">
    <div id="frame3" class="frame lazy" 
      data-src-small="https://source.unsplash.com/random/400x200?sig=3"
      data-src-large="https://source.unsplash.com/random/1200x600?sig=3">
      <p>3</p>
    </div>
  </a>

  <a href="#">
    <div id="frame4" class="frame lazy" 
      data-src-small="https://source.unsplash.com/random/400x200?sig=4"
      data-src-large="https://source.unsplash.com/random/1200x600?sig=4">
      <p>4</p>
    </div>
  </a>

  <a href="#">
    <div id="frame5" class="frame lazy" 
      data-src-small="https://source.unsplash.com/random/400x200?sig=5"
      data-src-large="https://source.unsplash.com/random/1200x600?sig=5">
      <p>5</p>
    </div>
  </a>

</main>

CodePen here

But I'd like to refactor the code to dry it up. I'm thinking for loops can be used to replace each of the 5 lists under a REFACTOR LIST: comment. My aim is to enable the code for any unknown number of divs with a class of frame.

To start, as an example, I've attempted to refactor the variable declarations at the beginning with the following loop:

var FramesQuantity = document.getElementsByClassName("frame").length
var frameVariables = [];

function createframeVariables() {
  for (var i = 0; i <= FramesQuantity; ++i) {
    var frameIndex = 'frame' + i;
    console.log("frameIndex: " + frameIndex);
    frameVariables[i] = document.getElementById(frameIndex);
  }
  return frameVariables;
}

createframeVariables();

console.log("frameVariables[0]: " + frameVariables[0]);

but that second console log returns null and I'm not sure if this is the right direction anyway.

Any ideas?


Solution

  • As was suggested, I was able to refactor the code, DRYing it up, by using .forEach with .querySelectorAll which obviated the need to set variables:

    // for loop demo
    document.querySelectorAll('.frame[data-src-small]').forEach( (frame, index) => {
      console.log( "index: " + index);
      console.log( "frame.dataset.srcSmall: " + frame.dataset.srcSmall);
      console.log( "frame.dataset.srcLarge: " + frame.dataset.srcLarge);
    })
    
    // create Lazy loader
    var myLazyLoad = new LazyLoad({
      elements_selector: ".lazy"
    });
    
    // load images responsively
    function loadImgs(context) {
    
      console.log('Loading images ' + context);
      
      if(window.matchMedia("only screen and (max-width:700px)").matches) {
        // viewport is less than or equal to 700 pixels wide
        document.querySelectorAll('.frame[data-src-small]').forEach( (frame, index) => {
          var srcSmall = frame.dataset.srcSmall;
          // set data-src for lazy loader
          frame.setAttribute('data-src', srcSmall);
          // tell lazy loader that the data should be re-processed
          frame.removeAttribute('data-was-processed');
        })
      } else {
        document.querySelectorAll('.frame[data-src-small]').forEach( (frame, index) => {
          // viewport is greater than 700 pixels wide
          var srcLarge = frame.dataset.srcLarge;
           // set data-src for lazy loader
          frame.setAttribute('data-src', srcLarge);
          // tell lazy loader that the data should be re-processed
          frame.removeAttribute('data-was-processed');
        })
      } 
      
      // tell lazy loader to update
      myLazyLoad.update();
    }
    
    // load images initially
    loadImgs("initially");
    
    // reload images when window is resized across the 700px breakpoint
    var lastWindowSize = window.innerWidth;
    window.onresize = function(event) {
      var currentWindowSize = window.innerWidth; 
      if((lastWindowSize <= 700 && currentWindowSize > 700) || (lastWindowSize > 700 && currentWindowSize <= 700)) {
        loadImgs("on resize across breakpoint");
      }
      lastWindowSize = currentWindowSize;
    };
    

    Forked updated version of the original CodePen here.