transform scale works incorrectly for odd pixel widths

我的未来我决定 提交于 2019-12-01 03:50:55

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.

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>


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>

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.

Browsers are notoriously bad at calculating stuff. There was a time when web developer math stated that (in some browsers) 33.33% times 3 was larger than 100% (but that was 14 years ago). Things have gotten much better since then, but don't rely on it. Doing resize tricks like this is not the way to go.

It seems to me that you want to resize the wrapper, while keeping the background size the same. To do so, you are using a complex transform trick, which (unprefixed) excludes 17% of all internet users. That is improper browser support and another reason not to do this.

This effect can be easily achieved with 99.99% browser support, working on all sizes.

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

.bg {
  width: 20px;
  height: 20px;
  display: block;
  position: absolute;
  top: 201px;
  left: 201px;
  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 {
  width: 4020px;
  height: 4020px;
}
<div id="wrapper" class="wrapper">
  <div id="bg" class="bg"></div>
</div>

If you want it to be responsive (you do!), this should do the trick:

* {padding: 0; margin: 0;}
html, body {height: 100%;}

.wrapper {
  width: 50vw;
  background-color: blue;
  position: relative;
  padding-bottom: 50%;
}

.bg {
  width: 20px;
  height: 20px;
  display: block;
  position: absolute;
  top: 25vw;
  left: 25vw;
  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 {
  width: 500vw;
  padding-bottom: 500%;
}
<div id="wrapper" class="wrapper">
  <div id="bg" class="bg"></div>
</div>
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!