Search code examples
javascriptjqueryloopsclasswidth

HTMLElement within any type of loop returns 0 or wrong value for all Width attributes


I have this thing going on:

$.Slider = function (elt) {
  //getting main elements
  var _this = this;
  this.elt = (elt instanceof $) ? elt : $(elt);
  this.wrapper = this.elt.find('.artworks_wrapper');

  //creating wrapper template for the triggers
  var sliderNav = document.createElement('div');
  $(sliderNav).css({
    width: '100%',
    height: '100%',
    display: 'flex',
    flexDirection: 'row',
    flexWrap: 'nowrap',
    justifyContent: 'space-between',
    position: 'absolute',
    top: '0',
    left: '0',
    zIndex: '1',
  });

  /*position relative for the global slider wrapper
  * easier element placement
  */
  this.elt.css('position', 'relative');

  this.elt.append(sliderNav);

  //creating triggers' template
  var triggerSample = document.createElement('div');
  $(triggerSample).css({
    width: '20%',
    height: '100%',
    backgroundImage: 'url("https://cdn.pixabay.com/photo/2014/03/25/16/59/left-297787_960_720.png")',
    backgroundSize: '50% 50%',
    backgroundRepeat: 'no-repeat',
    backgroundPosition: 'center',
    position: 'absolute',
    top: '0',
    cursor: 'pointer',
  });

  //specializing left and right triggers
  $leftTrigger = $(triggerSample);
  $rightTrigger = $leftTrigger.clone(true);

  $leftTrigger.css('left', '0');

  $rightTrigger.css('transform', 'scaleX(-1)');
  $rightTrigger.css('right', '0');

  //appending to sliderNav
  $(sliderNav).append($leftTrigger);
  $(sliderNav).append($rightTrigger);

  //getting needed datas about the slides
  this.slides = this.elt.find('.art_container');
  this.slidesNb = this.slides.length;
  this.maxDecal = this.slidesNb - 1;

  this.width = parseInt(this.elt.width());
  this. hmiddle = this.width / 2;

  //decal management variable
  this.decal = 0;
  this.decals = [this.hmiddle];

  //IT WORKS HERE WTF???????
  for (var i = 0; i < this.slidesNb; i++) {
    var actual = this.slides.get(i);
    var actualWidth =
      actual.offsetWidth +
      parseInt($(actual).css('margin-left')) +
      parseInt($(actual).css('margin-right'));
    console.log(actual);console.log(actualWidth);
    var actualHalfWidth = actualWidth / 2;
    _this.decals[i] -= actualHalfWidth;
    _this.decals[i + 1] = _this.decals[i] - actualHalfWidth;
  }

  this.nav = {
    left: $leftTrigger,
    right: $rightTrigger,
  };
};

$.Slider.prototype = {
  adjustView: function ()
  {
    if (this.decal === 0) {
      $(this.nav.left).css('visibility', 'hidden');
    } else if (this.decal === this.maxDecal) {
      $(this.nav.right).css('visibility', 'hidden');
    } else {
      $(this.nav.left).css('visibility', 'visible');
      $(this.nav.right).css('visibility', 'visible');
    }
  },

  adjustPos: function ()
  {
    var newPose = this.decals[this.decal];
    $(this.wrapper).offset({ left: newPose });
  },

  adjust: function ()
  {
    this.adjustView();
    this.adjustPos();
  },

  InitEvents: function ()
  {
    var _this = this;
    $(this.nav.left).click(function ()
      {
        if (_this.decal > 0) {
          _this.decal--;
          _this.adjust();
        }//else doesn't move
      }
    );

    $(this.nav.right).click(function ()
      {
        if (_this.decal < _this.maxDecal) {
          _this.decal++;
          _this.adjust();
        }//else doesn't move
      }
    );
  },
};

$(document).ready(function () {
  $('.artworks_slider').each(function () {
    var slider = new $.Slider($(this));
    slider.adjust();
    slider.InitEvents();
  });
});

$(document).resize(function () {
  $('.artworks_slider').each(function () {
    var slider = new $.Slider($(this));
    slider.adjust();
    slider.InitEvents();
  });
});
html { width: 100%; max-width: 100%; }
body { width: 100%; height: 100vh; }
main
{
  width: 100%;
  height: 100%;
}

/*------FLEX------*/
.flex_container { display: flex; }

.flex_container.horizontal
{
  -webkit-flex-flow: row nowrap;
  -ms-flex-flow: row nowrap;
  flex-flow: row nowrap;
}
.flex_container.vertical
{
  -webkit-flex-flow: column nowrap;
  -ms-flex-flow: column nowrap;
  flex-flow: column nowrap;
}

.flex_container.aligned_content
{
  -ms-align-items: center;
  align-items: center;
}
.flex_container.centered_content
{
  justify-content: center;
  -ms-align-items: center;
  align-items: center;
}
.flex_container.spaced_content
{
  justify-content: space-between;
  -ms-align-items: center;
  align-items: center;
}
.flex_container.stretched_content
{
  justify-content: space-around;
  -ms-align-items: center;
  align-items: center;
}

/*------ART DISPLAY------*/
.artworks_wrapper
{
  width: 100%;
  height: 100%;

  position: absolute;
}
.artworks_wrapper > .art_container:nth-child(1) { margin-left: 20px; }
.artworks_wrapper > .art_container:last-child { margin-right: 20px; }

.art_container
{
  margin: 0 10px;
  position: relative;
}
.art_container > .art_image.portrait { max-height: 60vh; }
.art_container > .art_image.paysage { max-width: 70vh; }

.art_container > .art_informations
{
  padding: 5px;

  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0
}

.artworks_slider
{
  width: 100%;
  height: 80%;

  overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body>
<main class="flex_container horizontal centered_content">
  <div class="artworks_slider">
    <div class="artworks_wrapper flex_container horizontal aligned_content">
      <figure class="art_container">
        <img
          class="art_image portrait"
          src="https://cdn.pixabay.com/photo/2018/07/08/14/16/cat-3523992_960_720.jpg"
          alt="Portrait d'Albator">
        <figcaption class="art_informations">
          <section class="art_description">
            <h2>stuff</h2>
            <p>
              
            </p>
          </section>
          <section class="art_data">
            
          </section>
        </figcaption>
      </figure>
      <figure class="art_container">
        <img
          class="art_image paysage"
          src="https://cdn.pixabay.com/photo/2018/06/23/16/22/romanesco-3493007_960_720.jpg"
          alt="">
        <figcaption class="art_informations">
          <section class="art_description">
            <h2>stuff</h2>
            <p>
              
            </p>
          </section>
          <section class="art_data">
            
          </section>
        </figcaption>
      </figure>
      <figure class="art_container">
        <img
          class="art_image portrait"
          src="https://cdn.pixabay.com/photo/2018/07/08/14/16/cat-3523992_960_720.jpg"
          alt="">
        <figcaption class="art_informations">
          <section class="art_description">
            <h2>stuff</h2>
            <p>
              
            </p>
          </section>
          <section class="art_data">
            
          </section>
        </figcaption>
      </figure>
      <figure class="art_container">
        <img
          class="art_image paysage"
          src="https://cdn.pixabay.com/photo/2018/06/23/16/22/romanesco-3493007_960_720.jpg"
          alt="">
        <figcaption class="art_informations">
          <section class="art_description">
            <h2>stuff</h2>
            <p>
              
            </p>
          </section>
          <section class="art_data">
            
          </section>
        </figcaption>
      </figure>
    </div>
  </div>
</main>
</body>

I don't get it... calls are working here but not at home. console.log() is within loop and first displays the HTMLElement then it's offsetWidth. Only things changed here are the images. When checking the images offsetWidth values they are right, but still they are wrong when called with HTMLElement.offsetWidth...

Help? Anyone?

EDIT: Deleted 'emotions' in body. I think it all comes down to execution order: script executes well after images get loaded within memory.


Solution

  • The issue is that images aren't loaded yet when you want to get their width.

    I played already way too long on that one...
    But here is a CodePen where I mainly changed this:

    for (var i = 0; i < this.slidesNb; i++) {
      var actual = this.slides.get(i);
      var actualWidth =
        actual.offsetWidth +
        parseInt($(actual).css('margin-left')) +
        parseInt($(actual).css('margin-right'));
      console.log(actual);console.log(actualWidth);
      var actualHalfWidth = actualWidth / 2;
      _this.decals[i] -= actualHalfWidth;
      _this.decals[i + 1] = _this.decals[i] - actualHalfWidth;
    }
    

    Into that:

    // How many images
    var imgCollection = this.wrapper.find("img");
    var imgCount = imgCollection.length;
    console.log(imgCount + " images.");
    
    var imgLoaded = 0;
    
    imgCollection.each(function(){
    
      $(this).on("load",function(){
        imgLoaded++;
    
        // When all images are loaded
        if(imgLoaded==imgCount){
          console.log("All images loaded");
    
          for (var i = 0; i < _this.slidesNb; i++) {
            var actual = _this.slides.get(i);
            var actualWidth = $(actual).outerWidth(true);
            console.log(actualWidth);
            var actualHalfWidth = actualWidth / 2;
            _this.decals[i] -= actualHalfWidth;
            _this.decals[i + 1] = _this.decals[i] - actualHalfWidth;
          }
          console.log("-----");
          console.log(_this.decals);
        }
      });
    });
    

    So if you already get the idea... Once ALL images are loaded, go the you measurement loop.

    And I changed the .offset() for an .animate() for fun.
    ;)