Keep div overlay relative to background image regardless of window size
I want to overlay some text on top of a background image with background-size: cover
.
The problem is how to keep the overlay div in the same position relative to the background image regardless of the window size?
There is a fiddle here: http://jsfiddle.net/resting/2yr0b6v7/
So, I want to place the word eye
above the cat's eye, regardless of the size of the window.
CSS or JS solutions are appreciated.
source to share
EDIT: Added js alternative
I was convinced that it could be done with css and almost gave up, but then I remembered the new (ish) units of css vh and vw ....
CSS
html, body{
height: 100%;
width: 100%;
}
.cat {
height: 100%;
width: 100%;
position: relative;
background:url(http://placekitten.com/g/800/400) no-repeat center center / cover;
}
.place-on-eye {
position: absolute;
color: #fff;
margin:0;
}
@media (min-aspect-ratio: 2/1) {
.place-on-eye {
bottom: 50%;
left: 46.875%;
margin-bottom: 1.25vw;
}
}
@media (max-aspect-ratio: 2/1) {
.place-on-eye {
bottom: 52.5%;
left: 50%;
margin-left: -6.25vh;
}
}
Explanation
So the left eye is at about 375, 190, and since the image is centered, we also need to know how far from the center, so 25, 10. Since the image covers, the image will resize depending on whether the ratio is the aspect ratio of the viewport is larger or smaller than the aspect ratio of the background image. Knowing this, we can use media queries to place text.
The image is 2: 1, so when the aspect ratio of the viewport is> 2: 1, we know that the width of the image is the same as the width of the viewport, so the left position <p>
should always be 46.867% (375/800). The bottom position will be more difficult because the image extends beyond the top and bottom of the viewport. We know the image is centered, so move it <p>
to the middle first and then raise it 2.5% (10/400) of the image height. We do not know the height of the image, but we know the aspect ratio of the image to the width of the image equal to the width of the viewport, so 2.5% of the height = 1.25% of the width. Therefore, we need to move the bottom 1.25% of the width, which we can do by setting margin-bottom:1.25vw
. By the way, we can do it withoutvw
in this case, because the padding is always calculated relative to the width, so we could set padding-bottom:1.25%
, however, it won't work in the next case where you have to put the left relative to the height.
The case where the aspect ratio is <2: 1 is similar. Image height is the height of the viewport, so the bottom position should always be 52.5% (210/400) and the left calculation is similar to the one above. Move it to the center, then set it back to 3.125% (25/800) of the image width, which is 6.25% of the image height, which is equal to the height of the viewport, so margin-left:-6.25vh
.
Hope this is correct and helps you!
Alternative to JS
Here's an alternative that uses js. It uses some features like forEach and bind which can cause problems depending on how old the browser you need to work, but they are easily replaceable. With js you can calculate the scaled dimensions of the bg image directly, which makes positioning easier. Not the most elegant code, but here goes:
//elem: element that has the bg image
//features: array of features to mark on the image
//bgWidth: intrinsic width of background image
//bgHeight: intrinsic height of background image
function FeatureImage(elem, features, bgWidth, bgHeight) {
this.ratio = bgWidth / bgHeight; //aspect ratio of bg image
this.element = elem;
this.features = features;
var feature, p;
for (var i = 0; i < features.length; i++) {
feature = features[i];
feature.left = feature.x / bgWidth; //percent from the left edge of bg image the feature resides
feature.bottom = (bgHeight - feature.y) / bgHeight; //percent from bottom edge of bg image that feature resides
feature.p = this.createMarker(feature.name);
}
window.addEventListener("resize", this.setFeaturePositions.bind(this));
this.setFeaturePositions(); //initialize the <p> positions
}
FeatureImage.prototype.createMarker = function(name) {
var p = document.createElement("p"); //the <p> that acts as the feature marker
p.className = "featureTag";
p.innerHTML = name;
this.element.appendChild(p);
return p
}
FeatureImage.prototype.setFeaturePositions = function () {
var eratio = this.element.clientWidth / this.element.clientHeight; //calc the current container aspect ratio
if (eratio > this.ratio) { // width of scaled bg image is equal to width of container
this.scaledHeight = this.element.clientWidth / this.ratio; // pre calc the scaled height of bg image
this.scaledDY = (this.scaledHeight - this.element.clientHeight) / 2; // pre calc the amount of the image that is outside the bottom of the container
this.features.forEach(this.setWide, this); // set the position of each feature marker
}
else { // height of scaled bg image is equal to height of container
this.scaledWidth = this.element.clientHeight * this.ratio; // pre calc the scaled width of bg image
this.scaledDX = (this.scaledWidth - this.element.clientWidth) / 2; // pre calc the amount of the image that is outside the left of the container
this.features.forEach(this.setTall, this); // set the position of each feature marker
}
}
FeatureImage.prototype.setWide = function (feature) {
feature.p.style.left = feature.left * this.element.clientWidth + "px";
feature.p.style.bottom = this.scaledHeight * feature.bottom - this.scaledDY + "px"; // calc the pixels above the bottom edge of the image - the amount below the container
}
FeatureImage.prototype.setTall = function (feature) {
feature.p.style.bottom = feature.bottom * this.element.clientHeight + "px";
feature.p.style.left = this.scaledWidth * feature.left - this.scaledDX + "px"; // calc the pixels to the right of the left edge of image - the amount left of the container
}
var features = [
{
x: 375,
y: 190,
name: "right eye"
},
{
x: 495,
y: 175,
name: "left eye"
},
{
x: 445,
y: 255,
name: "nose"
},
{
x: 260,
y: 45,
name: "right ear"
},
{
x: 540,
y: 20,
name: "left ear"
}
];
var x = new FeatureImage(document.getElementsByClassName("cat")[0], features, 800, 400);
source to share
According to how you set the position and size of the background image:
background-position:center center;
background-size:cover;
the center of the background image should still be in the center of the screen - this is useful as a constant, so try doing the same with your p.place-on-eye
.place-on-eye {
...
top: 50%;
left: 50%;
}
Right now, the top left corner is in the center of the screen, if you add width and height properties as well, you can actually center the elements in the center of the screen. So here:
.place-on-eye {
...
width:50px;
height:50px;
text-align:center /* to make sure the text is center according to elements width */
top: 50%;
left: 50%;
margin:-25px 0 0 -25px;
}
So, the center p.place-on-eye
is in the exact center of your screen, just like the center of your background image. To get this over the cat's eye, only offset the left and top edges as needed.
so something like margin:-27px 0 0 -60px;
should do it.
source to share
I made a fiddle borrowing principles from two answers. The black dot must overlap at the end of the line. But this solution deviates slightly from the real place in certain proportions.
Can someone improve it?
JS:
$(function() {
function position_spot() {
w = $(window).width();
h = $(window).height();
wR = w/h;
// Point to place overlay based on 1397x1300 size
mT = 293;
mL = -195;
imgW = 1397;
imgH = 1300;
imgR = imgW/imgH;
tR = mT / imgH; // Top ratio
lR = mL / imgW; // Left ratio
wWr = w / imgW; // window width ratio to image
wHr = h / imgH; // window height ratio to image
if (wR > imgR) {
// backgroundimage size
h = imgH * wWr;
w = imgW * wWr;
} else {
h = imgH * wHr;
w = imgW * wHr;
}
$('.overlay-spot').css({
'margin-top': h * tR,
'margin-left': w * lR
});
}
$(window).resize(function() {
position_spot();
});
position_spot();
});
source to share