масштаб преобразования работает неправильно для нечетной ширины пикселей

Я пытаюсь масштабировать div, но сохраняю внутренний элемент в такой же позиции и одинакового размера. Для этого я использую transform: scale(value) в оболочке и transform: scale(1/value) во внутреннем div.

Проблема в том, что внутренний div смещается, когда я меняю масштаб. Это происходит только в том случае, если ширина/высота обертки нечетная или не целая. Этого не происходит для четной ширины/высоты обертки.

Моя цель состоит в том, чтобы иметь много дочерних элементов оболочки, которые масштабируются вместе с оболочкой, но только один, который этого не делает.

Взгляните на этот пример, чтобы увидеть проблему в действии (наведите курсор, чтобы масштабировать).

Пример с нет проблем, внутренний элемент остается фиксированным в масштабе (высота и ширина контейнера четные):

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>

Пример с issue, внутренний элемент немного смещается по шкале (высота и ширина контейнера нечетные):

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>

Как я могу исправить эту проблему и избежать перемещения элементов по шкале независимо от размера контейнера?

PS : пример, использованный выше, является очень упрощенным примером, показывающим проблему, и он не является необходимым выводом или используемым кодом. Поэтому мы не ищем другого способа добиться того же поведения, что и выше, так как это довольно легко сделать.


person sanjihan    schedule 31.12.2017    source источник
comment
я добавил больше пояснений к вопросу и рабочий фрагмент, надеюсь, он привлечет больше внимания;) и, как вы можете видеть в моем ответе, похоже, это ошибка, связанная с расчетом масштаба или чем-то еще, что я не вижу :) надеюсь вы получите больше ответов   -  person Temani Afif    schedule 01.01.2018
comment
похоже больше не ответишь :(   -  person Temani Afif    schedule 07.01.2018
comment
Bounty обычно привлекает наибольшее внимание прямо перед тем, как истечет срок его действия :) есть надежда :D   -  person sanjihan    schedule 07.01.2018
comment
Просто используйте класс для масштабирования определенных вещей при наведении. Апскейлинг и даунскейлинг одновременно совершенно не нужны. Ты делаешь это неправильно ;-).   -  person JoostS    schedule 08.01.2018
comment
ну, я думаю, мы закончим тем, что скажем, что это ошибка ... очень жаль, что я не могу дать свой ответ за награду: p, поэтому он достанется одному из других   -  person Temani Afif    schedule 09.01.2018


Ответы (3)


Сначала я подумал, что это связано с вычислениями, выполненными браузером, и некоторым округлением, но, похоже, это ошибка. Я сделал много тестов, и независимо от значения шкалы, которую я использую, она всегда терпит неудачу при нечетном значении.

Вот простой пример только с 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>

Как вы можете видеть ниже, браузер добавляет дополнительный пиксель к внутреннему элементу div, но если вы присмотритесь, внутренний элемент имеет правильный размер, но он смещается на 1 пиксель вправо. Таким образом, блок инструментов Dev Tools позиционируется правильно, но не сам элемент! Таким образом, кажется, что браузер правильно рассчитал позицию, но сделал неправильную отрисовку.

введите здесь описание изображения

Та же проблема возникает, если мы просто применяем шкалу к контейнеру. Так что это не потому, что масштаб внутреннего элемента:

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>

введите здесь описание изображения


Даже если мы используем плавающее значение со шкалой, где мы можем сказать, что есть некоторые округления и сложные вычисления, у нас есть правильный вывод с четными значениями и проблема с нечетными значениями:

Пример с масштабом (1,25) и масштабом (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>

Пример с масштабом (1,33) и масштабом (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>

person Temani Afif    schedule 01.01.2018
comment
Да, ты прав. это проблема рисования, Chrome имеет такой эффект, кроме браузера FireFox. Но getBoundingClientRect возвращает правильные значения, как показывают DevTools. - person Avirtum; 09.06.2021

Просто не помещайте один из этих div в другой, вместо этого поместите их оба в третий div следующим образом:

.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>

Таким образом, вам просто не нужно будет масштабировать внутренний div до исходной высоты и ширины.

person Cheslab    schedule 31.12.2017
comment
этот код является частью более крупной программы Angular. Что масштабируется, так это сам компонент. Дочерние элементы находятся внутри компонента и являются частью необходимой логики. Я не могу поместить их вне компонента. Поэтому я не могу использовать ваш код. Но все равно спасибо. - person sanjihan; 31.12.2017
comment
@sanjihan вам действительно нужно масштабировать обертку? Если бы в реальном приложении у него был сплошной цветной фон, может быть, подойдет контур? Скажем, outline: solid blue 100px; вместо transform: scale(2); (с исходным html-кодом)? - person Cheslab; 31.12.2017

Браузеры известно плохо в расчетах. Было время, когда математика веб-разработчиков утверждала, что (в некоторых браузерах) 33,33% умножить на 3 больше, чем 100% (но это было 14 лет назад). С тех пор все стало намного лучше, но не полагайтесь на это. Подобные трюки с изменением размера — не лучший выход.

Мне кажется, что вы хотите изменить размер обертки, сохранив при этом размер фона. Для этого вы используете сложный прием преобразования, который (без префикса) исключает 17 % всех пользователей интернета. Это неправильная поддержка браузера и еще одна причина не делать этого.

Этого эффекта можно легко добиться благодаря поддержке браузеров на 99,99%, работающих со всеми размерами.

.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>

Если вы хотите, чтобы он был отзывчивым (вы делаете!), Это должно помочь:

* {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>

person JoostS    schedule 08.01.2018
comment
Другая альтернатива в этом случае проста, но цель вопроса состоит НЕ в том, чтобы иметь альтернативный способ, а в том, чтобы объяснить поведение и предоставить исправление для этой проблемы с масштабом (если она, конечно, есть) или выделить потенциальную ошибку. , пример был очень упрощен, чтобы показать проблему. Вы думаете, что мы будем утруждать себя сложным преобразованием масштаба, имея маленькое изображение и фон? конечно нет, он используется с более сложным кодом и нужен в этом коде. - person Temani Afif; 08.01.2018
comment
и я также думаю о расчете, но, как вы можете заметить, вы можете умножить на 2, а затем разделить на два, где нет необходимости округлять или делать какие-либо десятичные дроби ... это просто работа с четным значением, а не с нечетным значением. Вы можете проверить мой более упрощенный пример: stackoverflow.com/a/48048934/8620333 - person Temani Afif; 08.01.2018
comment
Я понимаю вашу точку зрения, но «не нужно округлять» и «не округлять» — это две разные вещи. Я обычно обрабатываю вычисления CSS так же, как обрабатываю плавающие числа SQL. Я думаю, что не полагаться на них — это общий подход (уже более 10 лет). Мне нравится ваш вопрос, но результат меня не удивляет. - person JoostS; 08.01.2018
comment
это не удивляет вас, но меня удивляет :) ... если бы это вообще не работало, я бы не удивился, если бы у меня всегда был один и тот же результат, я бы не удивился ... но если бы для значения из 200px работает нормально, а не для 201px, тут я удивился, особенно когда вычисление довольно простое... умножаем все на 2, а потом часть делим на 2 (так что во всех случаях делится на 2). Так что для меня это странно, так как теперь браузер очень хорошо обрабатывает вращение, 3D-перспективу, перекос и т. д. - person Temani Afif; 08.01.2018
comment
Верно.... хорошее замечание. Меня интересует результат. Похоже на ЕЩЕ ОДНУ глупую ошибку вычисления в CSS, что я и пытаюсь подчеркнуть: это не первый случай, когда сверхпростые вычисления CSS терпят неудачу. - person JoostS; 08.01.2018
comment
поскольку у нас нет более подходящего ответа, и поскольку я не могу присудить свою награду за свой ответ, вы его получите :) ... и я думаю, что это закончится ошибкой, так как мой ответ получил много голосов, поэтому я думаю, что никто не найдет подсказка для этого;) Кстати, я обновил свой ответ с лучшим объяснением .. и это кажется ошибкой в ​​​​процессе рисования, поскольку расчет кажется в порядке. - person Temani Afif; 10.01.2018
comment
Спасибо. Вы можете сообщить об ошибке в системе отслеживания ошибок Chrome со ссылкой на эту тему. . Может быть, они смогут найти ответ. - person JoostS; 10.01.2018