Как сделать имитацию background-size: contain на чистом js?
- есть элемент с размером 200px по ширине и 100 по высоте
- есть контейнер с динамичным размером (можно менять высоту и ширину, например, как в textarea)
Как сделать так,
- чтобы элемент сохранял соотношение сторон?
- и если размер контейнера 500х500, то чтобы элемент растянулся на всю ширину (500px) и на всю возможную высоту (250px), то есть с сохранением соотношения сторон
Необходима реализация именно на JS, так как размеры и элемента и контейнера приходят из json и я не могу применить к этим элементам стили.
Вот иллюстрация:
Красный - контейнер
Зелёный - элемент, который должен динамично подстраиваться под размеры родителя с сохранением соотношения сторон.

Ответы (1 шт):
Начнём с самого начала. Во-первых, вот вам разница между
background-size: cover
#block {
width: 500px;
height: 250px;
outline: 1px solid;
background-image: url('https://i.bcow.xyz/fMGVpco_d.webp');
background-size: cover;
}
<div id="block"><div>
И background-size: contain
#block {
width: 500px;
height: 250px;
outline: 1px solid;
background-image: url('https://i.bcow.xyz/fMGVpco_d.webp');
background-size: contain;
}
<div id="block"><div>
И если вы всё-таки хотите contain, тогда это будет очень ресурсозатратно и легче тогда будет что-нибудь намудрить CSS, но всё-таки сделать на нём.
А теперь вот рассмотрим решение вашей проблемы на JS:
class BackgroundCover {
constructor(selector) {
this.el = document.querySelector(selector)
}
__setProperties(image) {
let bcr = this.el.getBoundingClientRect()
image.style.cssText = `
position: absolute;
left: ${bcr.left}px;
top: ${bcr.top}px;
width: ${bcr.width}px;
height: ${bcr.height}px;
z-index: -1;`
}
init(src) {
let img = document.createElement('img')
img.src = src
this.__setProperties(img)
document.querySelector('body').appendChild(img)
let obs = new MutationObserver(() => {
this.__setProperties(img)
})
obs.observe(this.el ,{
attributes: true,
subtree: true,
childList: true,
})
for (let i = 0; i < 2; i++) {
(i < 1 ? window : this.el).addEventListener('resize', () => {this.__setProperties(img)})
}
}
}
let selectors = ['#actual', '#left1', '#left2', '#nav']
let images = [
'https://i.bcow.xyz/fMGVpco_d.webp',
'https://i.bcow.xyz/6vy9VVS_d.webp',
'https://i.bcow.xyz/lumjqf8_d.webp',
'https://i.bcow.xyz/6lhW59S_d.webp',
]
for (let i = 0; i < images.length; i++) {
new BackgroundCover(selectors[i] + ' .green')
.init(images[i])
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.red {
padding: 2px;
outline: 2px solid #ea0505;
}
.green {
padding: 2px;
outline: 2px solid #05ea05;
}
.green, .red {
filter: drop-shadow(rgb(0, 0, 0) 0 0 3px);
display: block;
}
body {
padding: 5px;
}
#nav {
width: calc(30% - 6px);
height: 20px;
margin-left: 5px;
}
#nav .green {
width: 40%;
height: 16px;
}
#content {
display: flex;
}
#content > div {
margin: 10px 5px;
}
#content > :first-child {
width: 30%;
}
#content > :last-child {
width: 70%;
}
#left1 {
height: 40px;
margin-bottom: 10px;
}
#left2 {
height: 80px;
}
#actual {
height: 130px;
}
#left1 .green {
height: 20px;
}
#left2 .green {
height: 40px;
}
#actual .green {
height: 50%;
resize: both;
overflow: auto;
}
<div id="nav" class="red">
<div class="green"></div>
</div>
<div id="content">
<div>
<div id="left1" class="red">
<div class="green"></div>
</div>
<div id="left2" class="red">
<div class="green"></div>
</div>
</div>
<div>
<div id="actual" class="red">
<div class="green"></div>
</div>
</div>
</div>
В данном случае решение просто покрывает весь задний фон элемента и динамически изменяется, есть ещё одно решение в котором изображение не искажается, но при этом уже задний фон будет замениться чёрным цветом:
class BackgroundCover {
constructor(selector) {
this.el = document.querySelector(selector)
}
__setProperties(element) {
let bcr = this.el.getBoundingClientRect()
let img = element.children[0]
element.style.cssText = `
position: absolute;
background-color: rgb(0, 0, 0);
left: ${bcr.left}px;
top: ${bcr.top}px;
width: ${bcr.width}px;
height: ${bcr.height}px;
z-index: -1;
overflow: hidden;`
img.style.left = 'calc(50% - ' + img.offsetWidth / 2 + 'px)'
}
init(src) {
let bg = document.createElement('div')
let img = document.createElement('img')
img.src = src
img.style.height = '100%'
img.style.position = 'relative'
img.onload = function() {
img.style.left = 'calc(50% - ' + img.offsetWidth / 2 + 'px)'
}
bg.appendChild(img)
this.__setProperties(bg)
document.querySelector('body').appendChild(bg)
let obs = new MutationObserver(() => {
this.__setProperties(bg)
})
obs.observe(this.el ,{
attributes: true,
subtree: true,
childList: true,
})
for (let i = 0; i < 2; i++) {
(i < 1 ? window : this.el).addEventListener('resize', () => {this.__setProperties(bg)})
}
}
}
let selectors = ['#actual', '#left1', '#left2', '#nav']
let images = [
'https://i.bcow.xyz/fMGVpco_d.webp',
'https://i.bcow.xyz/6vy9VVS_d.webp',
'https://i.bcow.xyz/lumjqf8_d.webp',
'https://i.bcow.xyz/6lhW59S_d.webp',
]
for (let i = 0; i < images.length; i++) {
new BackgroundCover(selectors[i] + ' .green')
.init(images[i])
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.red {
padding: 2px;
outline: 2px solid #ea0505;
}
.green {
padding: 2px;
outline: 2px solid #05ea05;
}
.green, .red {
filter: drop-shadow(rgb(0, 0, 0) 0 0 3px);
display: block;
}
body {
padding: 5px;
}
#nav {
width: calc(30% - 6px);
height: 20px;
margin-left: 5px;
}
#nav .green {
width: 40%;
height: 16px;
}
#content {
display: flex;
}
#content > div {
margin: 10px 5px;
}
#content > :first-child {
width: 30%;
}
#content > :last-child {
width: 70%;
}
#left1 {
height: 40px;
margin-bottom: 10px;
}
#left2 {
height: 80px;
}
#actual {
height: 130px;
}
#left1 .green {
height: 20px;
}
#left2 .green {
height: 40px;
}
#actual .green {
height: 50%;
resize: both;
overflow: auto;
}
<div id="nav" class="red">
<div class="green"></div>
</div>
<div id="content">
<div>
<div id="left1" class="red">
<div class="green"></div>
</div>
<div id="left2" class="red">
<div class="green"></div>
</div>
</div>
<div>
<div id="actual" class="red">
<div class="green"></div>
</div>
</div>
</div>
Очень надеюсь что я вас правильно понял и не писал всё зря)
Если возникли вопросы по коду, обязательно пишите их в комментариях.