0

Question: In the example below, when the target is clicked how do I return the correct, updated value of the left property? For example, if --moved is applied I'd like it to return 120px. If it is not applied I'd like it to return 100px.

Background: getComputedStyle can be used to return the values of css properties that are applied to an HTML element. However, in the case of elements that use absolute positioning it doesn't work as I had expected.

Example: In the simplified example below when the target is clicked the --moved class is either applied or removed from the target. However, for reasons that I don't understand after --moved is applied or removed getComputedStyle returns the value of left that was correct BEFORE the class was either added or removed. For example, when the target is clicked and --moved is added left is changed to 120px. Yet getComputedStyle still returns the original value of 100px.

Bonus Point: If you click the target rapidly the returned value is often fractional and doesn't return either 100px or 120px. This tells me that when using absolute positioned objects getComputedStyle doesn't return the css value, but in fact returns the current pixel coordinates of the target. How do I get the actual CSS values?

const myTarget = document.querySelector('.target');
const classForMoved = '--moved';

myTarget.addEventListener('click', () => {

    const doesTargetContainAdjustment = myTarget.classList.contains(classForMoved);

    if (doesTargetContainAdjustment === false) myTarget.classList.add(classForMoved);
    if (doesTargetContainAdjustment === true) myTarget.classList.remove(classForMoved);

    console.log(getComputedStyle(myTarget).left);
            
});
.target {
    position: absolute;
    top: 100px;
    left: 100px;
    width: 100px;
    height: 100px;
    background-color: red;
    transition: all 500ms linear;
}

.--moved {
    left: 120px;
}
<div class="target"></div>
myNewAccount
  • 578
  • 5
  • 17
  • 1
    The current CSS value for `left` is being set by the `transition`, so while that transition is active you indeed will get the value the transition sets at that time. To know what the "target" value is, I believe you'd have to parse the whole document's stylesheets yourself, which isn't a simple task. Why do you need to access this, maybe there is an easier way around your real issue. – Kaiido Aug 24 '22 at 03:11
  • 1
    If you're using dev tools in your browser, they should have a "computed" tab which is the actual CSS values for that element. This is what `getComputedStyle` returns. Since you have a transition, it doesn't move immediately (btw `transition: all` is generally bad practice). To get the final width, you would need to check AFTER your transition has ended. If you want to try and parse CSS rules, have a look at [this question](https://stackoverflow.com/questions/2952667/find-all-css-rules-that-apply-to-an-element) – Rylee Aug 24 '22 at 03:14
  • 1
    There are shortcomings in either method. If you need to know the actual width in JS, it'd be worth setting it via JS or on in the style attribute where you can reliable query it. – Rylee Aug 24 '22 at 03:16
  • 1
    @Rylee if they did set it by JS there would be no need to even *query* it, they'd already have it available. – Kaiido Aug 24 '22 at 03:24
  • @Kaiido hence why the part about querying is in reference to the style attribute (after "or"). Aside from that, there are cases where there's no need to store the value in a separate variable since you already have access to it via the style property (very quick). You would only store it in a specific variable if it's something you needed to access regularly, otherwise you end up with unnecessary code. So saying there's "no need" is incorrect. – Rylee Aug 25 '22 at 02:16
  • @Rylee my bad I missed that "or" in that sentence, maybe my brain removed it with the next "on", I don't know ;-). However, querying the style attribute is actually unreliable. It may return a different value than what you did set https://jsfiddle.net/aqnszgmh/ (, and it may not return the currently applied value, but that's true also for the stored JS variable). It's always more reliable to store instead of querying. – Kaiido Aug 25 '22 at 02:30
  • @Kaiido color values are unreliable (but can be made reliable with very little code). Most other properties, like positions (such as `left`), will return the actual value specified in the style attribute. Unless of course it is an invalid value for that property. Querying is as reliable, if not more as it gets the actual *valid* value from the browser at that time, rather than a cached one. This can be very important in async operations. – Rylee Aug 25 '22 at 04:23

1 Answers1

1
  • You have clearly mentioned that if click on .target immediately it will give current left position.
  • SO what's happening in your code is when you click it will add class (which will start animation), however, the function is synchronous it won't wait till transition gets complete so, it will calculate current left position immediately and return it.

You have to keep in mind that transion-duration:500ms means .target will take 500ms to complete its transition, that's why if you just call you getComputedStyle(myTarget).left after or at completion of transition-duration, which is 500ms, then you will receive expected left value.

  • I have just added promise to completeAction() function inside of click event which will wait till transition gets complete.
completeAction().then((value) => {
    console.log(value)
  })
  • Above, code will just run and print value.

const myTarget = document.querySelector('.target');
const classForMoved = '--moved';
style = window.getComputedStyle(myTarget)
duration = +style.getPropertyValue('transition-duration').substring(0, 3) * 1000;
myTarget.addEventListener('click', () => {

  const doesTargetContainAdjustment = myTarget.classList.contains(classForMoved);
  const completeAction = () => {
    return new Promise((resolve, rejected) => {
      if (doesTargetContainAdjustment === false) {
        myTarget.classList.add(classForMoved);
        setTimeout(() => {
          resolve(getComputedStyle(myTarget).left);
        }, duration);
      }
      if (doesTargetContainAdjustment === true) {
        myTarget.classList.remove(classForMoved);
        setTimeout(() => {
          resolve(getComputedStyle(myTarget).left);
        }, duration);
      }
    })
  }
  completeAction().then((value) => {
    console.log(value)
  })
});
.target {
  position: absolute;
  top: 100px;
  left: 100px;
  width: 100px;
  height: 100px;
  background-color: red;
  transition: all 500ms linear;
}

.--moved {
  left: 120px;
}
<div class="target"></div>
Nexo
  • 2,125
  • 2
  • 10
  • 20