0

I have 2 buttons with the same html structure on the same page. I have some js code to set the width depending on the text length of the button text. I need the js code to affect each individual button.

Please see the codepen here: https://codepen.io/gianpiero-di-lullo/pen/gOYxoVr

I've inserted them in an each() loop

$(".LIbtnWrapper").each(function(){

    var frontW = $(".LIbtnFront h1").width(),
      frontH = $(".LIbtnFront h1").height(),
      backW = $(".LIbtnBack h1").width(),
      backH = $(".LIbtnBack h1").height();

    if(frontW > backW) {
      $(".LIbtnBack h1").width(frontW);
    } else {
       $(".LIbtnFront h1").width(backW);
    }
})

I expect each button to set their own frontFace and backface width based on their text length and also behave independently on mouse events

Mitya
  • 33,629
  • 9
  • 60
  • 107
Jp_UK81
  • 1
  • 1
  • It's unclear to me what the issue is. – EternalHour Sep 02 '19 at 20:38
  • Do you also wish the mouse click/down to behave on only one button pair at a time as well? As it stands now, it changes both of them – Mark Schultheiss Sep 02 '19 at 21:09
  • The selectors depend on the HTML, this needs to be included in the question itself (codepen link is helpful, but all the code must be in the question incase, in the future, codepen is not available). – freedomn-m Sep 03 '19 at 06:44

4 Answers4

2

The problem is there's no relation between your callback context and the way you're targeting the inner elements.

$(".LIbtnWrapper").each(function(){
    var frontW = $(".LIbtnFront h1").width(),

The selector doesn't know that you mean the h1 in the element being iterated over - you're using a general selector, so it will just use the width of the first one it finds in the DOM matching the selector.

Instead, tightly couple your selectors to your callback context.

$(".LIbtnWrapper").each(function(){
    var frontW = $(this).find("h1").width(), //<-- h1 inside current iterator element
Mitya
  • 33,629
  • 9
  • 60
  • 107
  • Thank you all! I've learned about "context" today! Apologies for being unclear, I'll try harder in the next post :) – Jp_UK81 Sep 03 '19 at 08:10
0

You can use $(this) inside each to select the processing element and find() to select the child:

var a = $(".LIbtnWrapper");
$.each(a,function(){
    var front = $(this).find(".LIbtnFront").find("h1");
    var back = $(this).find(".LIbtnBack ").find("h1");

    var frontW = front.width(),
      frontH = front.height(),
      backW = back.width(),
      backH = back.height();

    if(frontW > backW) {
      back.width(frontW);
    } else {
       front.width(backW);
    }
})
0

This is about context, and the element is each .LIbtnWrapper - the context. So you have to find the child element from that for the buttons.

More about context here https://stackoverflow.com/a/16422612/125981

$(".LIbtnWrapper").each(function(index, element) {
  let bf = $(element).find(".LIbtnFront").find("h1");
  let bb = $(element).find(".LIbtnBack").find("h1");
  let frontW = bf.width(),
    frontH = bf.height(),
    backW = bb.width(),
    backH = bb.height();
  console.log(frontW, frontH, backW, backH);
  if (frontW > backW) {
    bb.width(frontW);
  } else {
    bf.width(backW);
  }
});

TweenLite.set(".LIbtnWrapper", {
  perspective: 1000
});
TweenLite.set(".LIbtn", {
  transformStyle: "preserve-3d"
});
TweenLite.set(".LIbtnBack", {
  rotationX: -90,
  transformOrigin: "50% top",
  y: "100%"
});
TweenLite.set([".LIbtnBack", ".LIbtnFront"], {
  backfaceVisibility: "hidden"
});


$(".LIbtnWrapper").hover(
  function() {
    TweenLite.to($(this).find(".LIbtn"), .3, {
      rotationX: 90,
      ease: Power4.easeOut
    });
  },
  function() {
    TweenLite.to($(this).find(".LIbtn"), .5, {
      rotationX: 0,
      ease: Power4.easeOut
    });
  }
);

$(".LIbtnWrapper")
  .mouseup(function() {
    TweenLite.to($(this), .1, {
      transformOrigin: "center",
      scale: "1"
    });
    $(".LIbtnFace h1").css({
      color: "#fff"
    });

  })
  .mousedown(function() {
    TweenLite.to($(this), .04, {
      transformOrigin: "center",
      scale: ".96"
    });
    $(".LIbtnFace h1").css({
      color: "#00B1A7"
    });


  });
body {
  background-color: black;
  margin: 20px;
  font-family: Arial, sans-serif;
  padding: 20px 0px
}

.container {
  width: 80%;
  display: flex
}

.LIbtnWrapper {
  position: relative;
  float: left;
  margin: auto;
  cursor: pointer;
  -webkit-font-smoothing: antialiased;
}

.LIbtnFace {
  position: absolute;
  overflow: hidden;
}

.LIbtnFront,
.LIbtnBack {
  background-color: rgba(0 0 0 0);
}

.LIbtn-large {
  font-size: 30px;
}

.LIbtn-medium {
  font-size: 20px;
}

.LIbtn-small {
  font-size: 10px;
}

.LIbtnFace h1 {
  margin: auto;
  padding: 15px;
  border: solid 6px #fff;
  color: white;
  white-space: nowrap;
  text-align: center;
}

.LIbtnFace.LIbtnBack h1 {
  border-color: #00B1A7;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.1.3/TweenMax.min.js"></script>
<div class="container">
  <div class="LIbtnWrapper">
    <div class="LIbtn">
      <div class="LIbtnFace LIbtnFront">
        <h1 class="LIbtn-large">FRONT</h1>
      </div>
      <div class="LIbtnFace LIbtnBack">
        <h1 class="LIbtn-large">THIS IS THE BACK</h1>
      </div>
    </div>

  </div>
  <div class="LIbtnWrapper">
    <div class="LIbtn">
      <div class="LIbtnFace LIbtnFront">
        <h1 class="LIbtn-large">ANOTHER FRONT</h1>
      </div>
      <div class="LIbtnFace LIbtnBack">
        <h1 class="LIbtn-large">BACK</h1>
      </div>
    </div>

  </div>
</div>
Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100
  • Note you can also somewhat simplify maintenance of the other functions below that for `$(".LIbtnWrapper")` but that is perhaps a different question. – Mark Schultheiss Sep 02 '19 at 21:06
0

jQuery's .each takes two arguments: index and element

$('.LIbtnWrapper').each(function(index, element){
    // log the current element in this iteration
    console.log('the current element: ', element);

    // get child element with context of this/element
    var thisH1 = $('.LIbtnFront h1', element);
    console.log('the child h1 of the current element', thisH1);
});

This is the "jQuery" way of accessing the current element and its children as shown in the api examples.

digital-pollution
  • 1,054
  • 7
  • 15
  • While this works I do not believe "best" would be this form – Mark Schultheiss Sep 03 '19 at 03:21
  • explain @MarkSchultheiss . It has slower execution time but is easier to maintain, and selecting dom nodes is pretty much the fastest thing you can do with javascript in the browser. – digital-pollution Sep 03 '19 at 03:31
  • From experience, most developers will find `$(element).find('.LIbtnFront h1');` a bit easier to understand than `$('.LIbtnFront h1', element);` - so really simply from a maintenance perspective I would add preference for the former. Or even `$(element).find('.LIbtnFront').find('h1');` even – Mark Schultheiss Sep 03 '19 at 03:40
  • ah so just preference then, I prefer not to chain dom traversal methods one after the other unless absolutely necessary, so we'll have to agree to disagree there. – digital-pollution Sep 03 '19 at 04:20
  • I chain them on purpose due to the right to left processing in the sizzle engine which makes it faster in a large DOM. I often see things like `$('#myid div span.myclass')` which is just really bad internally. – Mark Schultheiss Sep 03 '19 at 04:24
  • 1
    "best" is, of course, subjective. Is it the best "efficiency"? Possibly. Is it the best "readable"? Completely depends on the reader; most would find using `this` more readable: `$(".libetc", this)`. There's a reason SO has a close-vote for when questions ask for the "best" way to do something. – freedomn-m Sep 03 '19 at 06:52
  • edited to remove the controversial "best" and just say that's whats shown in the api docs. There is no difference between using "this" or the "element" argument, although "this" will have a different context if an arrow function is used in the callback, "element" may also be more readable if the callback is passed to a another named function to keep the argument names consistent. Most likely "element" is provided for consistency across the jQuery api. – digital-pollution Sep 03 '19 at 20:21