Issue
I want to keep 'pin' elements absolutely positioned above a background image. They must be positioned above the exact same part of the image content, regardless of aspect ratio. The image has object-fit: cover, and an intrinsic aspect ratio of 16:9, so on lower aspect ratios e.g. 4:3, the left and right sides of the image will be cropped out. Therefore, a pin positioned at left: 30% will now be above a different part of the image.
Here's a simplified version of the current setup:
I have a div containing an image:
<body>
<div class="pin-container">
<img src="./test.jpg" class="main-image"/>
</div>
</body>
In my JS, pins are added to the 'pin-container' div:
const pins = [
{
xPercent: 30,
yPercent: 50,
text: 'Pin A',
},
{
xPercent: 80,
yPercent: 20,
text: 'Pin B',
},
{
xPercent: 50,
yPercent: 50,
text: 'Pin C',
},
];
const pinContainer = document.querySelector('.pin-container');
pins.forEach((pinDetails) => {
const pin = document.createElement('div');
pin.className = 'pin';
const pinText = document.createElement('p');
pinText.className = 'pin-text';
pinText.innerText = pinDetails.text;
pin.appendChild(pinText);
pin.style.left =
pinDetails.xPercent *
(window.innerWidth / window.innerHeight / (16 / 9)) +
'%';
pin.style.top = pinDetails.yPercent + '%';
pinContainer.appendChild(pin);
});
And the CSS dictates that the img element and pin-container div should both occupy the entire screen, with the pin elements absolutely positioned on top:
.pin-container {
height: 100vh;
width: 100vw;
position: relative;
}
.main-image {
position: absolute;
height: 100%;
width: 100%;
object-fit: cover;
object-position: 75%;
}
.pin {
position: absolute;
z-index: 1;
background-color: white;
padding: 10px;
border-radius: 50%;
}
The issue is with the formula I'm attempting to use in JS - just getting the difference in aspect ratios and multiplying the xPercent value by that difference is not enough. Doing so affects all pins equally - if the screen aspect ratio is 4:3, the formula works like this for a pin with an xPercent of 30:
pinDetails.xPercent * (window.innerWidth / window.innerHeight / (16 / 9)) 30 * (4/3 / (16/9)) 30 * (1.33... / 1.77...) 30 * 0.75 = 22.5%
As it should be, the value of the 'left' property is reduced, pushing the pin further towards the left side of the screen and correctly accounting for the left side of the image being cropped out.
However, that applies equally to ALL pins, regardless of their position. So a pin with an xPercent of 80 will also have its left property reduced, pushing it further to the left. In that case, though, because the image has object-position: center, the right side of the image will also be cropped out, and in fact the pin at left: 80% should have its left property INCREASED, pushing it further to the right to account for the right side of the image being cropped out.
As far as I can tell, this would need to be on a curve - a pin at 50% should have its left value unchanged, as the center of the image will always be in the same place, and pins close to 50% should have their left property changed very little. As pins get closer to 0%, the difference in aspect ratio should lower their left value more and more, and as pins get closer to 100%, it should RAISE their left value more and more.
What I can't work out is exactly how the formula needs to change to achieve this, or if I'm just approaching the whole problem wrong and there's a simpler way to achieve the effect I want.
For bonus points - the important content of the image is actually clustered on the right side, so I would ideally like to set .main-image object-position: 75%. That seems to add a whole extra layer of complexity to the issue though, so it might be best to deal with a centered image first!
Any help will be massively appreciated!!
Solution
Not sure if this is what you need, but I came up with a simpler solution: I apply scaling and centering to a parent element that contains both the map and the markers. That way the markers will always remain at the correct spot.
In this example there should be a marker on the letter O and the letter D in the image. The markers remain in place if you resize the window.
A problem is that the markers will also scale with the image, I fixed that by "reverse scaling" the markers when they are placed.
https://jsfiddle.net/xgLp48n0/8/
const container = document.querySelector("#container")
const map = document.querySelector("#map")
const img = document.querySelector("img")
let scale
function cover() {
let viewW = container.getBoundingClientRect().width
let viewH = container.getBoundingClientRect().height
let imgW = img.naturalWidth
let imgH = img.naturalHeight
scale = Math.max((viewW / imgW), (viewH / imgH))
let xdiff = (viewW / 2) - (imgW / 2)
let ydiff = (viewH / 2) - ( imgH / 2)
let offsetX = ((imgW - viewW) * scale) / 2
let offsetY = ((imgH * scale) - viewH) / 2
map.style.transform = `translate(-${offsetX}px, -${offsetY}px) scale(${scale})`
}
function placePin(x, y) {
const pin = document.createElement("div")
pin.classList.add("pin")
map.appendChild(pin)
pin.style.transform = `translate(${x}px, ${y}px) scale(${1/scale})`
}
cover()
placePin(840, 1020)
placePin(1430, 940)
window.addEventListener("resize", (event) => cover());
#container {
width: 80vw;
height: 60vh;
outline: 2px solid blue;
overflow: hidden;
}
.pin {
position: absolute;
border-radius: 50%;
background-color: red;
width: 16px;
height: 16px;
}
#img {
position: absolute;
opacity:0.8;
}
#map {
transform-origin: center;
position: relative;
}
<div id="container">
<div id="map">
<div id="img">
<img src="https://images.unsplash.com/photo-1455849318743-b2233052fcff?q=80&w=2669&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" >
</div>
</div>
</div>
Answered By - Kokodoko
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.