Reputation: 79268
I would like to have the behavior of CSS image cover, where it fills the container element but doesn't change the proportions of the image; whatever doesn't fit into the viewport of the container element extends beyond the container and is hidden.
However, the way this works in CSS (from how I've used it so far) is it positions it around the center of the image.
I would like to say "set the cover anchor point to x = 150 and y = 375 of the image" type of thing. Wondering how to do that. This way, I could pick a spot on the image and have that be the center point of the cover.
Upvotes: 2
Views: 6858
Reputation: 5954
Using background-size: cover
extremely limits the quantity of coordinates that can be centered due to how cover
itself works.
background-size: cover
enlarges (or decreases the size of) an image in order to always display as much of the image as possible while still being able to cover the whole container.
Let's visualize some examples:
In Case #1, the image needs to be enlarged, so that the image's width
matches the container's width
.
In Case #2, the image needs to be enlarged, so that the image's height
matches the container's height
.
In case #3, the image needs to be shrunk, so that the image's height
matches the container's height
.
As you may have noticed, the image will always be centered on one axis already. This means that you can only center the image on the remaining axis. Furthermore, only coordinates that fall between the two red dots or on the red line, which is only 1px
wide in reality, can be centered, otherwise the image wouldn't be able to cover the entire container.
background: cover
is not sufficient. You need to use JS.
In order to devise a solution, you need to know...
After already spending hours trying to figure this out, I literally just realized that it's completely impractical to do what you want to do because the output image will become infinitely larger the closer the desired center coordinates are to the edge of the original image. It was really tricky figuring out a formula to produce the proper scale of the output image because the scale is also a variable. The only practical way to go about this is to indeed limit what coordinates can be centered by, say, applying a no-go zone around the outer edges of the image like a border (i.e. anything in this area cannot or should not be centered). The width of the so-called border would depend entirely on the resolution of the image.
Here is what I was working on. You're than welcome to continue where I left off, albeit I have to warn you that the code is a mess right now. I was in the process of wrapping my head around how to properly scale and position the image while maintaining a cover state. A background in math would help big time. Good luck.
const
srcImg = document.querySelector('#source-image'),
output = document.querySelector('#output')
let
srcImgWidth, srcImgHeight
const
test = document.querySelector('#test')
window.onload = ()=>{
srcImgWidth = srcImg.width
srcImgHeight = srcImg.height
}
srcImg.onclick = function(e){
const
ctrWidth = output.offsetWidth,
ctrHeight = output.offsetHeight,
compAxisX = ctrWidth / srcImgWidth,
compAxisY = ctrHeight / srcImgHeight,
rect = srcImg.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top
// create cover
if (compAxisX > compAxisY){
//console.log('width grow/shrink to match width')
output.style.backgroundSize = `${ctrWidth}px ${ctrWidth / srcImgWidth * srcImgHeight}px`
} else if (compAxisY > compAxisX) {
//console.log('height grow/shrink to match height')
output.style.backgroundSize = `${ctrHeight / srcImgHeight * srcImgWidth}px ${ctrHeight}px`
} else {
// square in square ???
output.style.backgroundSize = `${ctrWidth}px ${ctrHeight}px`
}
// determine scale of image
const
compAxisX1 = ctrWidth / 2 / x,
compAxisY1 = ctrHeight / 2 / y
let
qtrImplicitViewportX,
qtrImplicitViewportY,
scale
// cover container with implicit viewport
if (compAxisX1 > compAxisY1){
//console.log('width grow/shrink to match width')
qtrImplicitViewportX = ctrWidth / 2
qtrImplicitViewportY = ctrWidth / 2 / x * y
//srcImgWidth / x * scale * srcImgWidth + 'px'
//srcImgHeight / y * scale * srcImgHeight + 'px'
// x / srcImgWidth === qtrImplicitViewportY
newWidth = qtrImplicitViewportX / (x / srcImgWidth)
newHeight = qtrImplicitViewportY / (y / srcImgHeight)
console.log(newWidth, newHeight)
output.style.backgroundSize = `${newWidth}px ${newHeight}px`
output.style.backgroundPosition = '0% 100%'
} else if (compAxisY1 > compAxisX1){
//console.log('height grow/shrink to match height')
//qtrImplicitViewportX = ctrHeight / 2 / y * x
qtrImplicitViewportY = ctrHeight / 2
//srcImgWidth / x * scale * srcImgWidth + 'px'
//srcImgHeight / y * scale * srcImgHeight + 'px'
// x / srcImgWidth === qtrImplicitViewportY
newWidth = qtrImplicitViewportX / (x / srcImgWidth)
newHeight = qtrImplicitViewportY / (y / srcImgHeight)
console.log(newWidth, newHeight)
output.style.backgroundSize = `${newWidth}px ${newHeight}px`
output.style.backgroundPosition = '0% 100%'
} else {
}
test.style.width = newWidth + 'px'
test.style.height = newHeight + 'px'
test.style.bottom = output.getBoundingClientRect().bottom
test.style.left = output.getBoundingClientRect().left
}
#input-container {
padding: 10px;
display: inline-block;
background: grey;
}
#output {
width: 256px;
height: 377px;
resize: both;
position: relative;
overflow: auto;
box-sizing: border-box;
border: solid red 3px;
background-image: url('https://i.postimg.cc/s2PnSDmR/test0.png');
background-size: cover;
background-repeat: no-repeat;
}
#test {
z-index: -1;
width: 256px;
height: 377px;
position: absolute;
transform: translateY(-100%);
background: orange;
background-image: url('https://i.postimg.cc/s2PnSDmR/test0.png');
background-size: cover;
background-repeat: no-repeat;
}
<div id='input-container'>
<div>Click the Image to Center on Click Point</div>
<img id='source-image' src='https://i.postimg.cc/s2PnSDmR/test0.png' />
</div>
<div id='output'></div>
<div id='test'></div>
Upvotes: 1
Reputation: 33439
You need to use background-position
for a background image or object-position
for an image with objec-fit
you need to calculate the correct value by dividing the coordinate by the image dimension and multiply by 100%
background-position: calc(131 / 200 * 100%) calc(66 / 200 * 100%);
/* ^^^ ^^^ ^^ ^^^
||| ||| || |||
||| ||| || image height
||| ||| y coordinate
||| image width
x coordinate
*/
Click on the buttons and you will trigger animations, which show that the focus is on the bird in these images
document.addEventListener('click', e => {
if (e.target.nodeName.toLowerCase() == 'button') {
document.getElementById(e.target.dataset.id).classList.toggle('animate')
}
})
.container {
display: inline-block;
background-image: url(https://picsum.photos/id/990/200/200);
background-size: cover;
background-position: calc(131 / 200 * 100%) calc(66 / 200 * 100%);
transition: all 1s ease-in-out;
}
img {
-o-object-fit: cover;
object-fit: cover;
-o-object-position: calc(131 / 200 * 100%) calc(66 / 200 * 100%);
object-position: calc(131 / 200 * 100%) calc(66 / 200 * 100%);
transition: all 1s ease-in-out;
}
.animate[data-property=width] {
width: 5px !important;
}
.animate[data-property=height] {
height: 5px !important;
}
<span id="container-1" data-property="width" class="container" style="width:200px; height:100px"></span>
<span id="container-2" data-property="height" class="container" style="width:100px; height:200px"></span>
<img id="image-1" data-property="width" src="https://picsum.photos/id/990/200/200" alt="" style="width:200px; height:100px">
<img id="image-2" data-property="height" src="https://picsum.photos/id/990/200/200" alt="" style="width:100px; height:200px">
<br>
<button data-id="container-1">Animate</button>
<button data-id="container-2">Animate</button>
<button data-id="image-1">Animate</button>
<button data-id="image-2">Animate</button>
Upvotes: 3