Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

transform scale works incorrectly for odd pixel widths

I am trying to scale a div, but keep the inside element at the same position and the same size. To do that, I use transform: scale(value) on wrapper and transform: scale(1/value) on the inside div.

The problem is, that the inside div shifts when I change scale. That only happens if width/height of wrapper is odd or not whole. It does not happen for even widths/height of the wrapper.

My goal is to have many child elements of wrapper that scale alongside wrapper, but only one that does not.

Take a look at this example to see problem in action (hover to scale).

Example with no issue, inner element stay fixed on scale (height and width of container are even):

https://jsfiddle.net/o16rau6u/5/

.wrapper {
  width: 200px;
  height: 200px;
  background-color: blue;
  position: relative;
}

.bg {
  width: 20px;
  height: 20px;
  display: inline-block;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -10px;
  margin-left: -10px;
  background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Wiktionary_small.svg/350px-Wiktionary_small.svg.png");
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

.wrapper:hover {
  transform: scale(2);
}

.wrapper:hover .bg {
  transform: scale(0.5);
}
<div id="wrapper" class="wrapper">
  <div id="bg" class="bg"></div>
</div>

Example with issue, the inner element move a little on scale (height and width of container are odd):

https://jsfiddle.net/o16rau6u/6/

.wrapper {
  width: 201px;
  height: 201px;
  background-color: blue;
  position: relative;
}

.bg {
  width: 20px;
  height: 20px;
  display: inline-block;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -10px;
  margin-left: -10px;
  background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Wiktionary_small.svg/350px-Wiktionary_small.svg.png");
  background-size: 100% 100%;
  background-repeat: no-repeat;
}

.wrapper:hover {
  transform: scale(2);
}

.wrapper:hover .bg {
  transform: scale(0.5);
}
<div id="wrapper" class="wrapper">
  <div id="bg" class="bg"></div>
</div>

How can I fix this issue and avoid my elements to move on scale whataver the size of container is ?

PS : The example used above is a very simplified example to show the issue and it's not the needed output or the code used. So we are not looking for another way to achieve the same behavior above as it's pretty easy to be done.

like image 938
sanjihan Avatar asked Dec 31 '17 16:12

sanjihan


People also ask

How does a transform scale work?

The scale() CSS function defines a transformation that resizes an element on the 2D plane. Because the amount of scaling is defined by a vector, it can resize the horizontal and vertical dimensions at different scales. Its result is a <transform-function> data type.

How do you scale pixels in CSS?

The scale property in CSS resizes an element's width and height in proportion. So, if we have an element that's 100 pixels square, scaling it up by a value of 2 doubles the dimensions to 200 pixels square. Similarly, a scale value of . 5 decreases the dimensions in half, resulting in 50 pixels square.


2 Answers

At the start I thought this is related to the calculation done by the browser and some rounding but it's seems to be bug. I have done a lot of test and whataver the value of the scale I use it always fail on odd value.

Here is a simple example with only scaleX

body:after {
  content: "";
  position: absolute;
  z-index: 999;
  top: 0;
  bottom: -200%;
  width: 2px;
  right: 50%;
  margin-right: -1px;
  background: rgba(0, 0, 0, 0.5);
}

.box {
  width: 200px;
  height: 100px;
  margin: 50px auto;
  background: blue;
  position: relative;
}

.inner {
  height: 20px;
  width: 20px;
  background: red;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -10px;
  text-align: center;
  color: #fff;
  margin-top: -10px;
}
<div class="box">
  <div class="inner">A</div>
</div>

<div class="box" style="transform:scaleX(2)">
  <div class="inner" style="transform:scaleX(0.5)">A</div>
</div>

<div class="box" style="width:201px;transform:scaleX(2)">
  <div class="inner" style="transform:scaleX(0.5)">A</div>
</div>

As you can see below, the browser seems to add an extra pixel to inner div, but if you look more closely the inner div has a correct size but it's being translated by 1px to the right. So the hover block of Dev Tools is positioned correctly but not element itself! So it seems that the browser correctly calculated the position but did a wrong painting.

enter image description here

The same issue appear if we simply apply scale on the container. So it's not because the scale of inner element:

body:after {
  content: "";
  position: absolute;
  z-index: 999;
  top: 0;
  bottom: -200%;
  width: 2px;
  right: 50%;
  margin-right: -1px;
  background: rgba(0, 0, 0, 0.5);
}

.box {
  width: 200px;
  height: 100px;
  margin: 50px auto;
  background: blue;
  position: relative;
}

.inner {
  height: 20px;
  width: 20px;
  background: red;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -10px;
  text-align: center;
  color: #fff;
  margin-top: -10px;
}
<div class="box" style="transform:scaleX(2)">
  <div class="inner">A</div>
</div>

<div class="box" style="width:201px;transform:scaleX(2)">
  <div class="inner">A</div>
</div>

enter image description here


Even if we use floating value with scale where we can say there is some rouding and complex calculation, we have correct output with even values and issue with odd values:

Example with scale(1.25) & scale(1/1.25):

body:after {
  content: "";
  position: absolute;
  z-index: 999;
  top: 0;
  bottom: -200%;
  width: 2px;
  right: 50%;
  margin-right: -1px;
  background: rgba(0, 0, 0, 0.5);
}

.box {
  width: 200px;
  height: 100px;
  margin: 50px auto;
  background: blue;
  position: relative;
}

.inner {
  height: 20px;
  width: 20px;
  background: red;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -10px;
  text-align: center;
  color: #fff;
  margin-top: -10px;
}
<div class="box">
  <div class="inner">A</div>
</div>

<div class="box" style="transform:scaleX(1.25)">
  <div class="inner" style="transform:scaleX(0.8)">A</div>
</div>

<div class="box" style="width:201px;transform:scaleX(1.25)">
  <div class="inner" style="transform:scaleX(0.8)">A</div>
</div>

Example with scale(1.33) & scale(1/1.33):

body:after {
  content: "";
  position: absolute;
  z-index: 999;
  top: 0;
  bottom: -200%;
  width: 2px;
  right: 50%;
  margin-right: -1px;
  background: rgba(0, 0, 0, 0.5);
}

.box {
  width: 200px;
  height: 100px;
  margin: 50px auto;
  background: blue;
  position: relative;
}

.inner {
  height: 20px;
  width: 20px;
  background: red;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -10px;
  text-align: center;
  color: #fff;
  margin-top: -10px;
}
<div class="box">
  <div class="inner">A</div>
</div>

<div class="box" style="transform:scaleX(1.33)">
  <div class="inner" style="transform:scaleX(calc(1 / 1.33))">A</div>
</div>

<div class="box" style="width:201px;transform:scaleX(1.33)">
  <div class="inner" style="transform:scaleX(calc(1 / 1.33))">A</div>
</div>
like image 69
Temani Afif Avatar answered Oct 22 '22 22:10

Temani Afif


Just don't put one of these divs into another, instead put both of them into the third div like this:

.wrapper {
  width: 201px;
  height: 201px;
  position: relative;
}

.div-1 {
  width: 100%;
  height: 100%;
  background-color: blue;
}

.div-1:hover {
  transform: scale(2);
}

.div-2 {
  width: 20px;
  height: 20px;
  display: inline-block;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-top: -10px;
  margin-left: -10px;
  background-image: url("https://upload.wikimedia.org/wikipedia/commons/thumb/f/f9/Wiktionary_small.svg/350px-Wiktionary_small.svg.png");
  background-size: 100% 100%;
  background-repeat: no-repeat;
}
<div class="wrapper">
  <div class="div-1"></div>
  <div class="div-2"></div>
</div>

This way you just wont be needed to scale the inner div back to it's original height and width.

like image 3
Cheslab Avatar answered Oct 22 '22 21:10

Cheslab