Lance Pollard
Lance Pollard

Reputation: 79268

How to set an anchor point for CSS img cover

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

Answers (2)

oldboy
oldboy

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:

Visual Breakdown

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.

Visual Breakdown

Visual Breakdown

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.




The Javascript Approach

In order to devise a solution, you need to know...

  1. the inherent dimensions of the image in order to maintain its proportion.
  2. the dimensions of the container at any given time, which is a variable.
  3. the coordinates that we want to center, which is also a variable.

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.

Self-Imposed Practical Limitations

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

yunzen
yunzen

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

Related Questions