How to "glue" elements to resizable image? - javascript

I have image with width: 100%; height: auto; and on this image are other elements with position: absolute; left: x; top: x; (it's a game).
When I was resizing the page, elements weren't moving (it was messing up), so I wrote this:
// on start
var img = document.getElementById('world_image');
startImageSize.w = img.clientWidth;
startImageSize.h = img.clientHeight;
actImageSize.w = startImageSize.w;
actImageSize.h = startImageSize.h;
// on resize
var img = document.getElementById('world_image');
actImageSize.w = img.clientWidth;
actImageSize.h = img.clientHeight;
// update (each tick)
$("#cr_" + this.ID).animate({
"left" : (this.x - ((startImageSize.w - actImageSize.w) / 2)) + "px",
"top" : (this.y - ((startImageSize.h - actImageSize.h) / 2)) + "px"
}, speed);
But it's not working corretly, here is short GIF representing what is happening:
A "red point" is moving faster than it shoud be.
// EDDIT! JSFiddle with whole project https://jsfiddle.net/BrunonDEV/18mqyd8r/1/
What am I doing wrong?

I wouldn't try to reposition those in JS - it's slow, makes resizing janky, and chews the javascript cycles you'll need for actually running your game, and it's not necessary anyway. Why not just set your dot's position in %, like this:
.map {
background: #68B1FF url('https://upload.wikimedia.org/wikipedia/commons/5/5e/BlankMap-World-Sovereign_Nations.svg') center top no-repeat;
background-size: contain;
position: relative;
height: 0;
padding-top: 51.2%; /* The original map image is 1104 x 566px. This is the aspect ratio as a percentage of height to width */
width: 100%;
}
.marker {
border-radius: 50%;
width: 1vw; /* These allow the markers to scale with the map */
height: 1vw;
background: red;
min-height: 8px;
max-height: 16px;
min-width: 8px;
max-width: 16px;
position: absolute;
}
<div class="map">
<div class="marker" style="top: 20%; left: 33%;"></div>
</div>
One of the important pieces here is fixed aspect ratio boxes in CSS - if you set height to 0, and padding-top (or bottom) to a percentage, the padding height is calculated as a percentage of the width, not the height as you might expect.
For bonus points, you should use an SVG map image - then it'll scale cleanly, too.

Related

Resizing images of different aspect ratios to fit a div without stretching it

I'm making a portfolio website and I am trying to make a simple image browser. I have a container div with size relative to the size of the browser window. I want that div to be able to contain images of different aspect ratios and a caption of fixed height and the width relative to the width of the image. I don't want the div to stretch to contain the images, I want to resize the image (you can see what I mean in the picture below).
illustration of the problem here
I was trying to use javascript to calculate the size of the image, but failed, because I couldn't calculate the element's size before it is actually loaded. This is how I tried to do it (not thinking about the titlebar):
var divAspectRatio = containerDiv.offsetHeight/containerDiv.offsetWidth;
var imageAspectRatio = image.offsetHeight/image.offsetWidth;
if(divAspectRatio>imageAspectRatio){
image.style.height = content_in.offsetHeight;
}else{
image.style.width = content_in.offsetWidth;
}
captionDiv.style.width = image.offsetWidth;
How do I make it work?
If your background image is in a div element, add this to your css, inside the div block:
background-size: cover;
If it's in an img element, add this to your css, inside the img block:
object-fit: cover;
Try using this css element:
object-fit: cover
This way, your image will get resized to fit the containing box!
The desired layout needs some calculation as the placing of an image that has aspect ratio bigger than the aspect ratio of the container differs from one that doesn't. Also to note is that the caption area is required to be only as wide as the displayed image but has a fixed height.
The imgs are wrapped in an 'innerdiv' which will also contain the caption. On loading the image's aspect ratio is found and stored as a CSS variable. Other CSS variables are set up in advance - see the head of this snippet for those that can be chosen. Remaining calculations are done in CSS.
window.onload = function () {
const imgs = document.querySelectorAll('.container .innerdiv .img');
imgs.forEach(img => {
img.parentElement.style.setProperty('--imgratio', img.naturalWidth / img.naturalHeight);
img.parentElement.classList.add(( (img.naturalWidth / img.naturalHeight) > getComputedStyle(img).getPropertyValue("--containerratio") ) ? 'wider' : 'thinner');
});
}
* {
padding: 0;
margin: 0;
}
.container {
/* SET THE NEXT 4 VARIABLES TO WHAT YOU REQUIRE */
--unit: 1vmin; /* the basic unit - must be fixed e.g. vmin, px, ch not % */
--containerw: 40; /* width of a container in these units */
--containerratio: 1.5; /* the ratio of width to height */
--captionh: 4vmin; /* height of a caption including its units (which must be fixed e.g. vmin, px, em */
--containerh: calc(var(--containerw) / var(--containerratio));
display: inline-block;
width: calc(var(--containerw) * var(--unit));
height: calc(var(--containerh) * var(--unit));
position: relative;
border: solid;
}
.innerdiv {
display: inline-block;
position: absolute;
top: 0;
left: 0;
}
.innerdiv.thinner {
width: calc(var(--imgratio) * ((var(--containerh) * var(--unit)) - var(--captionh)));
height: 100%;
left: 50%;
transform: translateX(-50%);
}
.innerdiv.wider {
width: 100%;
height: calc((var(--containerw) / var(--imgratio)) * var(--unit) + var(--captionh));
top: 50%;
transform: translateY(-50%);
}
.img {
width: 100%;
height: auto;
}
.caption {
font-size: 2vmin;
background-color: yellow;
text-align: center;
width: 100%;
height: var(--captionh);
position: absolute;
top: calc(100% - var(--captionh));
left: 0;
}
<div class="container">
<div class="innerdiv">
<img class="img" src="https://picsum.photos/id/1016/200/300">
<div class="caption">CAPTION</div>
</div>
</div>
<div class="container">
<div class="innerdiv">
<img class="img" src="https://picsum.photos/id/1016/500/200">
<div class="caption">CAPTION</div>
</div>
</div>
<div class="container">
<div class="innerdiv">
<img class="img" src="https://picsum.photos/id/1016/300/300">
<div class="caption">CAPTION</div>
</div>
</div>

Maintain aspect ratio of a video when resizing the browser

I'm working on a Video editing tool, and I need to maintain the 16:9 aspect ratio of the video when resizing the screen horizontally and vertically. So far I got it to work as expected when resizing horizontally, and when resizing down vertically, but can't get it to work when sizing up vertically. The Javascript code I used to calculate the height of the video and resize it is below (notice how the else clause is empty because that's where the code should go):
const calculateHeight = () => {
// Get the other elements on the page
const header = document.querySelector('.main-navigation');
const meshTopBar = document.querySelector('.mesh__top-bar');
const footer = document.querySelector('.mesh__bottom-bar');
// Get the section to apply the window height to it
const mainSection = document.querySelector('.insert-level-container');
// Get the video elements
const editor = document.querySelector('.mesh__insert-editor-container');
const video = document.querySelector('.mesh__insert-editor-video-container');
// Apply the height to the main section by calculating the window height minus the other elements' height
if(mainSection !== null) {
mainSection.style.height = (window.innerHeight - header.offsetHeight - meshTopBar.offsetHeight - footer.offsetHeight) + 'px';
}
// This should be the ideal height for the video
video.style.minHeight = ((video.offsetWidth * 9) / 16) + 'px';
// If the video height is bigger than the section height (calculated above), then resize it
if(video.offsetHeight + 115 > mainSection.offsetHeight) {
video.style.minHeight = video.offsetHeight - 1 + 'px';
editor.style.maxWidth = video.offsetHeight * 16 / 9 + 'px';
} else {
// This is where the logic for the vertical resizing should go
}
}
The relevant CSS for these items is:
.mesh__insert-editor-container {
margin-left: auto;
margin-right: auto;
}
.mesh__insert-editor-video-container {
position: relative;
width: 100%:
}
And the HTML:
<section class="mesh__insert-editor-container flex__one flex-container flex-column horizontally-left-aligned" id="video-main-container">
<div class="mesh__insert-editor-video-container flex-container horizontally-right-aligned flex-wrap">
<video class="mesh__insert-editor-video-placeholder"></video>
</div>
</section>
All this code is:
Get the height of all the elements on the page, sum them and calculate the main section height by subtracting that height;
If the video height gets bigger than the section height, I reduce its height by -1px each time the window gets resized, and calculate the new width.
All the above code is giving me this result, which works great for most scenarios, but I need the video to size up when the condition on the if statement is not met. Everything I tried inside the else statement gets "jumpy".
Any better alternatives to solve this would be much appreciated. Thanks all!
The CSS aspect ratio trick might be a good solution: https://css-tricks.com/aspect-ratio-boxes/
The approach takes advantage of a quirk in CSS where padding based on a percentage value will be relative to the element's width. Create a container using this trick, the important bit is this line:
padding-top: calc(9/16 * 100%);
The value is calculating the correct height based on the aspect ratio you want (9 tall over 16 wide in this case) and generating it relative to the width of the element by multiplying by 100%.
With the container maintaining aspect ratio, just place the content inside an absolute positioned inner div and you should be good. This solution is fully responsive at that point.
* { box-sizing: border-box }
.outer-max-width {
max-width: 960px;
margin: 0 auto;
}
.aspect-ratio-box {
width: 100%;
padding-top: calc(9/16 * 100%);
position: relative;
border: 2px solid red; /* for demo visibility, remove */
}
.aspect-ratio-box-content {
position: absolute;
width: 100%;
top: 0;
left: 0;
border: 2px solid blue; /* for demo visibility, remove */
}
.video-placeholder {
display: block;
width: 100%;
height: auto;
}
<div class="outer-max-width">
<div class="aspect-ratio-box">
<div class="aspect-ratio-box-content">
<img class="video-placeholder" src="https://placeimg.com/640/360/nature" />
</div>
</div>
</div>
Got it to work! I used this amazing CSS-only solution: https://codepen.io/EightArmsHQ/pen/BvNzrm similar to BugsArePeopleToo's suggestion, from eightarmshq:
.content{
position: absolute;
top: 0;
bottom: 0;
width: 100%;
height: 100%;
background: #555;
box-shadow: inset 1vh 1vh 10vh 0 rgba(0,0,0,0.5);
display: flex;
box-sizing: border-box;
border: 25px solid #cecece;
}

How to adjust the mouse position after rotating an object?

I am trying to create an eye that follows cursor movement.
I got the horizontal and vertical coordinate of the mouse and the browser width and height.
Everything works perfectly. Except that I used rotate(45 deg) on the design of the eye so now the ball is not moving in the right position.
I was thinking about a math equation that finds the distance between the old and new coords, but I am not sure how to implement it.
Here is the full code:
https://jsfiddle.net/Mr_MeS/3ym6kuec/3/
so this is the .eye where its rotated
.eye {
width: 37.5px;
height: 37.5px;
background: white;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(45deg);
border-radius: 75% 0;
overflow: hidden;
cursor: pointer;
}
.ball {
width: 7.5px;
height: 7.5px;
background: #222f3e;
border-radius: 50%;
border: 5px solid #576574;
position: relative;
top: 50%;
left: 50%;
}
and here is the JS that does the work and needs to be edited.
var balls = document.getElementsByClassName("ball");
document.onmousemove = function () {
var x = event.clientX * 100 / window.innerWidth + "%";
var y = event.clientY * 100 / window.innerHeight + "%";
//event.clientX => get the horizontal coordinate of the mouse
//event.clientY => get the Vertical coordinate of the mouse
//window.innerWidth => get the browser width
//window.innerHeight => get the browser height
for (var i = 0; i < 2; i++) {
balls[0].style.left = x;
balls[0].style.top = y;
balls[0].style.transform = "translate(-" + x + ",-" + y + ")";
}
}
Now, if I remove the rotation from the .eye, it works perfectly, expect that the whole shape doesn't look to be in position.
If I keep the 45deg rotation, the shape is good, but the ball moves wrongly.
You could try to put the eye-background (the white part that needs to rotate 45 degrees) into a div (or pseudo-element) that's inside the .eye element. In that way you don't need to rotate the container element, so the coordination of the ball element stays the same.
Another point, why are you using that for-loop? I think running the code once will be sufficient :)
EDIT: I've been playing around with your example a bit and fixed it. What happens is that if you rotate an element, the direction in which things will transform (and top/left positioning) will also change. So moving the element 10px to the left, will go 10px to the left, under a 45 degree angle, because it's rotated 45 degrees.
What I did now was to put an element (.inner) inside the eye div, which I gave a counter-rotation of -45 degrees. In this way, the container element of the ball has the correct orientation again, which fixes the problem: https://jsfiddle.net/bxprjvgL/
HTML:
<div class="eye">
<div class="inner">
<div class="shut"><span></span></div>
<div class="ball"></div>
</div>
</div>
CSS:
.inner {
width: 100%;
height: 100%;
transform: rotate(-45deg);
}

Centering an image with absolute position (top, left)

I am using parametric equations to position images around a circle (How do I calculate a point on a circle’s circumference?)
I am doing this in less, using simple math:
// x,y position on circumference:
// x = a + r * cos(t)
// y = b + r * sin(t)
.position {
left: #a + ( #r * cos(#t) );
top: #b + ( #r * sin(#t) );
}
The trouble I have is that this positions the image by the top left corner, not the center of the image, so doesn't account for the visual offset of height and width this has. Trying to adjust by height/2, width/2 doesn't work as the angle of each image is different.
Is there an easy way to position an image this way so it is centred on x,y?
.position {
left: #a + ( #r * cos(#t) );
top: #b + ( #r * sin(#t) );
margin-left: -#r;
margin-top: -#r;
}
Instead of calculations you simply could have done this though:
top:50%;
left:50%;
position:absolute;
margin-left:-#r;
margin-top:-#r;
FIDDLE DEMO
Subtracting Out Half-Width/Height Does Work
You stated you tried half-width/height calculations in your attempt to center the image on the circumference of a circle at some angular reference (as I understand your question). It seems that should and does work. Check out this...
Example Fiddle
The circle and image colors are for visual reference, but the image positioning is done by:
LESS
//define circle position
#r: 100px;
#x: 175px;
#y: 150px;
.setImage(#w, #h, #a) {
//#a is angle from HORIZONTAL but moves clockwise
//(based on radians unless units are given)
width: #w;
height: #h;
left: (#x + ( #r * cos(#a) ) - (#w / 2));
top: (#y + ( #r * sin(#a) ) - (#h / 2));
}
.test1 {
.setImage(40px, 40px, -35deg);
}
.test2 {
.setImage(60px, 30px, -80deg);
}
.test3 {
.setImage(90px, 20px, -150deg);
}
.test4 {
.setImage(40px, 70px, -240deg);
}
.test5 {
.setImage(20px, 90px, -295deg);
}
CSS Output
.test1 {
width: 40px;
height: 40px;
left: 236.9152044288992px;
top: 72.64235636489539px;
}
.test2 {
width: 60px;
height: 30px;
left: 162.36481776669305px;
top: 36.5192246987792px;
}
.test3 {
width: 90px;
height: 20px;
left: 43.39745962155612px;
top: 90px;
}
.test4 {
width: 40px;
height: 70px;
left: 104.99999999999996px;
top: 201.60254037844385px;
}
.test5 {
width: 20px;
height: 90px;
left: 207.26182617406997px;
top: 195.630778703665px;
}
Works Even With Rotated Images
Assuming you set your images to have transform-origin: center center then it works to keep them on center even if the images are rotated at some angle.
See Rotated Image Example Fiddle

Scale a div to fit in window but preserve aspect ratio

How can I scale a div to fit inside the browser view port but preserve the aspect ratio of the div. How can I do this using CSS and/or JQuery?
Thanks!
You don't need javascript for this. You can use pure CSS.
A padding-top percentage is interpreted relative to the containing block width. Combine it with position: absolute on a child element, and you can put pretty much anything in a box that retains its aspect ratio.
HTML:
<div class="aspectwrapper">
<div class="content">
</div>
</div>
CSS:
.aspectwrapper {
display: inline-block; /* shrink to fit */
width: 100%; /* whatever width you like */
position: relative; /* so .content can use position: absolute */
}
.aspectwrapper::after {
padding-top: 56.25%; /* percentage of containing block _width_ */
display: block;
content: '';
}
.content {
position: absolute;
top: 0; bottom: 0; right: 0; left: 0; /* follow the parent's edges */
outline: thin dashed green; /* just so you can see the box */
}
The display: inline-block leaves a little extra space below the bottom edge of the .aspectwrapper box, so another element below it won't run flush against it. Using display: block will get rid of it.
Thanks to this post for the tip!
Another approach relies on the fact that browsers respect an image's aspect ratio when you resize only its width or height. (I'll let google generate a 16x9 transparent image for demonstration purposes, but in practice you would use your own static image.)
HTML:
<div class="aspectwrapper">
<img class="aspectspacer" src="http://chart.googleapis.com/chart?cht=p3&chs=160x90" />
<div class="content">
</div>
</div>
CSS:
.aspectwrapper {
width: 100%;
position: relative;
}
.aspectspacer {
width: 100%; /* let the enlarged image height push .aspectwrapper's bottom edge */
}
.content {
position: absolute;
top: 0; bottom: 0; right: 0; left: 0;
outline: thin dashed green;
}
Thanks to Geoff for the tip on how to structure the math and logic. Here's my jQuery implementation, which I'm using to size a lightbox so it fills the window:
var height = originalHeight;
var width = originalWidth;
aspect = width / height;
if($(window).height() < $(window).width()) {
var resizedHeight = $(window).height();
var resizedWidth = resizedHeight * aspect;
}
else { // screen width is smaller than height (mobile, etc)
var resizedWidth = $(window).width();
var resizedHeight = resizedWidth / aspect;
}
This is working well for me right now across laptop and mobile screen sizes.
I have a different pure HTML/CSS approach which does not rely on padding or absolute positioning. Instead it uses em units and relies on the CSS min() function plus a little bit of math.
Imagine that we want a viewport div with 16:9 aspect ratio which always fits the browser window and is centered in the axis with excess space. Here's how we can accomplish that:
HTML
<body>
<div class="viewport">
<p>
This should be a 16:9 viewport that fits the window.
</p>
</div>
</body>
CSS
body {
width: 100vw;
height: 100vh;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
background-color: white;
font-size: min(1vw, 1.778vh);
}
div.viewport {
width: 100em;
height: 56.25em;
background-color: lightblue;
}
div.viewport > p {
font-size: 3em;
text-align: center;
}
You can experiment with this in a sample JSFiddle here.
The secret sauce is in the body font-size. It should be set to min(1vw, Avh), where A is the aspect ratio you want the div to have, i.e. width / height. In the example above we're using 1.778, which is approximately 16 / 9.
In CSS, em units are based on the font-size of the element, which is inherited from parent element if not explicitly set. For your viewport div, set the width to 100em (NOT rem) and the height to Iem, where I is the inverse of the aspect ratio expressed as a percentage, i.e. 100 / A or 100 * height / width. In the example above we're using 56.25, which is 100 * 9 / 16.
One bonus of this approach is that all of your nested elements may also use em units so that they always scale precisely with the size of the viewport. You can see this used on the p element in the example.
Note that as an alternative to the above, you may set the font-size on your html element and use rem units everywhere. CSS rem units are similar to em units but always relative to the root element's font-size.
Javascipt:
//Responsive Scaling
let outer = document.getElementById('outer'),
wrapper = document.getElementById('wrap'),
maxWidth = outer.clientWidth,
maxHeight = outer.clientHeight;
window.addEventListener("resize", resize);
resize();
function resize(){
let scale,
width = window.innerWidth,
height = window.innerHeight,
isMax = width >= maxWidth && height >= maxHeight;
scale = Math.min(width/maxWidth, height/maxHeight);
outer.style.transform = isMax?'':'scale(' + scale + ')';
wrapper.style.width = isMax?'':maxWidth * scale;
wrapper.style.height = isMax?'':maxHeight * scale;
}
HTML:
<div id="wrap">
<div id="outer">
{{ fixed content here }}
</div>
</div>
Styling:
/* Responsive Scaling */
#wrap {
position: relative;
width: 1024px;
height: 590px;
margin: 0 auto;
}
#outer {
position: relative;
width: 1024px;
height: 590px;
transform-origin: 0% 0%;
overflow: hidden;
}
This is possible with JQuery and a bit of maths.
Use JQuery to get the view ports width and height as well as the divs current dimensions.
$(document).width();
Calculate the divs current aspect ratio. eg width/height
You need a bit of logic to determine whether to set the width or height first, then use the initial ratio to calculate the other side.
jQuery has a plugin that grows an object until one of it's sides reaches a certain px-value. Coupling this will the viewport's height, you could expand any element to that size: jQuery MaxSide.

Categories

Resources