Controlling size of a HTML element at fixed aspect ratio - javascript

None of the answers I've looked up quite works for my use case.
I want to fix the aspect ratio of a HTML element on my page (specifically, a canvas which I want to always be square) in such a way that
it's as large as fits in the alloted space;
it fits inside the browser window without scrolling.
I don't think this can be done by CSS alone (please prove me wrong). I did find various CSS tricks purported to do it, but they take into account only either the available width or the available height, not both.
I tried javascript that looks up the size (.parentElement.clientWidth and .parentElement.clientHeight) of the parent element, finds the smaller one and sizes the canvas according to that by setting canvas.style.width and canvas.style.height, but for some reason I don't understand this doesn't work - the canvas ends up too small and only resizes to the correct setting on a window resize. In this case, the parent element's size was determined by flexbox.
EDIT: the following code does almost what I am looking for. There is only one problem, which you can observe by:
loading the page in a non-maximed window
maximizing and un-maximizing that window.
The resulting layout is not the same as the original (directly after the load).
<!DOCTYPE html>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
margin: 0;
background: #404040;
height: 100vh;
min-width: 800px;
display: flex;
justify-content: center;
align-items: stretch;
}
main {
background: yellow;
flex: 0 1 100vh;
display: flex;
flex-direction: column;
}
.sidepanel {
background: blue;
flex: 0.2 0.4 250px;
text-align: center;
}
.spacer {
flex: 0.5 1 0;
}
#square {
background: black;
margin: auto;
}
</style>
<script type="module">
const canvas = document.querySelector('#square')
const parent = canvas.parentElement
const setSquare = () => {
const { clientWidth, clientHeight } = parent
const L = Math.min(clientWidth, clientHeight)
canvas.style.width = canvas.style.height = `${L}px`
}
window.addEventListener('resize', setSquare)
setSquare()
</script>
<div class="spacer"></div>
<section class="sidepanel">
</section>
<main>
<canvas id="square"></canvas>
</main>
<section class="sidepanel">
</section>
<div class="spacer"></div>

Related

How can I make divs dynamically fill up fixed-size container and shrink and/or expand depending on number of divs in the container?

Introduction
I am currently learning web development and I am trying to make some sort of etch-a-sketch thing and it should pop out a grid with up to 100 square cells per side BUT no matter what number of cells per side the grid itself should be using the same amount of space, so for example if the total space it could use was 1000px then the grid, no matter how many cells it has, will use 1000px, no more or no less. My problem is that I'm not sure what I should do to make the grid do this.
Problem context For starters, for my purposes I can only generate the grid itself via Javascript. In the Javascript I use a doubly-nested for loop where after each iteration in the first loop, a new "grid-row" div will be created and then the second for loop will populate that grid row with "square" divs, so if I set the number as 5 per-side, it will create a grid-row and populate that row with 5 square divs 5 times.
My problem The whole grid itself is wrapped in a "grid-container" div with a fixed size. I would like to make it so the grid will always fill up the size of the grid container but not exceed the container itself. In other words, I would like to find a way to make the square and/or grid-row divs be able to shrink or grow depending on the number of them inside the grid-container so as to not have the overall grid exceed the size of the grid-container itself but still fill up the available space in the container.
So far I have played around with various CSS properties suggestions that I found from Google searches such as setting height and width of either the "square" or "grid-row" divs (or both of them at the same time) to 100%, playing around with the max widths and heights, as well as their mins, and playing around with the "overflow" and object-fit properties. I'm not sure if there was a solution for my problem in Javascript and if there was I haven't found one in my myriad Google searches.
Here's my code snippet, I'd appreciate any help:
const container = document.querySelector(".grid-container");
for (let x = 0; x < 6; x++) {
/* Both the "x" and "y" variables are set to arbitrary values (up to 100). The "x" variable here represents how many total "rows" the grid will create. The following "y" variable represents how many square divs will populate each row, so both of these numbers should be the same to make a square grid.*/
let newRow = document.createElement('div');
newRow.classList = `grid-row`;
container.appendChild(newRow);
for (let y = 0; y < 6; y++) {
let square = document.createElement('div');
square.classList = `square`;
newRow.appendChild(square);
}
}
const gridCells = document.querySelectorAll(".square");
gridCells.forEach(cell => {
cell.addEventListener("mouseover", () => {
cell.classList.add("hov-square");
});
});
/*This is the current state of the CSS file that I left on prior to posting this question. I set the width and height of the "square" divs to 10px each to make the grid visible and to give an idea of what the grid may look like, but my goal is for the grid (the grid-rows and the square divs inside of it) to be able to fill up the "grid-container" div that wraps the grid itself and resize itself depending on how many square cells are in the grid but never exceed the size of the grid-container itself.*/
body {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
background-color: black;
}
.grid-container {
margin-top: 10%;
display: flex;
width: 750px;
height: 750px;
justify-content: center;
align-items: center;
overflow: visible;
}
.grid-row {
max-width: 100%;
max-height: 100%;
}
.square {
width: 10px;
height: 10px;
border: 1px dashed white;
}
.hov-square {
background-color: grey;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="script.js" defer></script>
<link rel="stylesheet" href="styles.css">
<title>Etch-a-Sketch</title>
</head>
<body>
<div class="grid-container"></div>
</body>
</html>
I grabbed the width of the grid-container, then divided that by the number of columns to get the width/height of each cell.
Then I used javascript to set the width and height to that size.
I got rid of the grid-row CSS. Also, you don't need to loop through the cells to add hover class. Just use .square:hover in CSS and it will automatically apply it to the matching cells.
const container = document.querySelector(".grid-container");
let squares = 100;
let _parent_width = getComputedStyle(container).width;
let sq_size = Math.floor(_parent_width.replace("px","") / squares) - 2;
for (let x = 0; x < squares; x++) {
let newRow = document.createElement('div');
newRow.classList = `grid-row`;
container.appendChild(newRow);
for (let y = 0; y < squares; y++) {
let square = document.createElement('div');
square.classList.add("square");
square.style.width = sq_size + "px";
square.style.height = sq_size + "px";
newRow.appendChild(square);
}
}
const gridCells = document.querySelectorAll(".square");
gridCells.forEach(cell => {
cell.addEventListener("mouseover", () => {
cell.classList.add("hov-square");
});
});
/*This is the current state of the CSS file that I left on prior to posting this question. I set the width and height of the "square" divs to 10px each to make the grid visible and to give an idea of what the grid may look like, but my goal is for the grid (the grid-rows and the square divs inside of it) to be able to fill up the "grid-container" div that wraps the grid itself and resize itself depending on how many square cells are in the grid but never exceed the size of the grid-container itself.*/
body {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
padding: 0;
background-color: black;
}
.grid-container {
display: flex;
width: 1000px;
height: 1000px;
justify-content: center;
align-items: center;
overflow: visible;
}
.square {
border: 1px dashed white;
}
.hov-square{
background-color: grey;
}
<div class="grid-container">c</div>

css scroll-snap: focusing on the element which got snapped to

I implemented a horizontal grid with some cards in them. The grid uses CSS scroll-snap and it works nicely when navigating with mouse/touchscreen.
The problem occurs when the grid is navigated using a keyboard. Pressing tab after navigating through the grid with arrow keys causes the view to jump back to the element that got the focus, not the card which is current snapped to.
My ideal behaviour when pressing tab is, to focus on the card which is currently snapped to.
Any suggestions to make this possible?
As far as I can tell there is currently no way to handle this natively. Nils Schwebel's answer is not going to be very elegant, but it looks like the best way to go.
Here's a working example:
Note: I've added quite a bit of pure decoration to make it easier to understand, so you may need to pick out the relevant parts after some testing.
const main = document.getElementById("Main"),
sections = document.getElementsByClassName("section");
main.addEventListener('scroll', (e) => {
// Grab the position yo are scrolled to (the top of the viewport)
let pos = main.scrollTop;
for (let i = 0, l = sections.length; i < l; i++) {
// Since our stap-align is centered, get the position of the middle of the viewport relative to the current section's top (if your snap items are not full-height, it might require using half the viewport's height instead)
let relativePos = sections[i].offsetTop - pos + (sections[i].offsetHeight / 2);
// Check if the point we found falls within the section
if (relativePos >= 0 && relativePos < sections[i].offsetHeight) {
sections[i].focus();
break;
}
}
});
body {
margin: unset;
}
main {
display: flex;
flex-wrap: wrap;
align-items: stretch;
justify-content: center;
width: 100%;
height: 100vh;
background-color: #222;
overflow-y: auto;
scroll-snap-type: y mandatory;
}
section {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
scroll-snap-align: center;
}
section:focus {
border: none;
outline: none;
}
#s1 {
background-color: #d72748;
}
#s2 {
background-color: #b51f7e;
}
#s3 {
background-color: #e64869;
}
#s4 {
background-color: #e79946;
}
section h2 {
color: white;
}
<main id="Main">
<section class="section" id="s1" tabindex="1" aria-labelledby="a1">
<h2 id="a1">AREA 1</h2>
</section>
<section class="section" id="s2" tabindex="1" aria-labelledby="a3">
<h2 id="a2">AREA 2</h2>
</section>
<section class="section" id="s3" tabindex="1" aria-labelledby="a2">
<h2 id="a3">AREA 3</h2>
</section>
<section class="section" id="s4" tabindex="1" aria-labelledby="a4">
<h2 id="a4">AREA 4</h2>
</section>
</main>
I would add a scroll listener and just check if the element is at the top of the scroll view. You may be able to modify one of these solutions: How to check if element is visible after scrolling?

Canvas rendering resolution - how to use a canvas in a flex layout

I'm using a flex environment to automatically scale a container with fixed sides and header (header loaded using jquery on document ready).
--------------
| header |
--------------
| |canvas| |
| | | |
--------------
There are 2 flex environments, one from the top to bottom (column) and one from left to right (row).
This seems to work normally when not taking the canvas into account.
As the canvas is scaled when the page loads and the flex is applied, its size changes while leaving the context's "render" width/height the same. This leads to the image being rendered at a small resolution and getting stretched.
To solve this issue, before rendering I run this function each time:
Map._resizeCanvas = function (canvas) {
let displayWidth = canvas.clientWidth;
let displayHeight = canvas.clientHeight;
if(displayWidth !== canvas.width || displayHeight !== canvas.height) {
canvas.width = displayWidth;
canvas.height = displayHeight;
}
};
This most of the time works but leads to an image that is strechted by a few pixels (along X).
Sometimes it causes the header to disappear or increase the page height therefore adding a scroll bar.
How can I use a canvas inside of a flex environment? Is there something I need to take into account when I set the canvas width? (like margins, paddings, I already set the display style to block, inline broke the entire paage)
Is there a way to set the context's rendering resolution without changing the layout and affecting the page?
HTML:
<body>
<div id="header-navigation"></div> <!-- set by jquery on document ready -->
<div id="page-container">
<div class="container" id="container1">
abc
<div style="height: 50pt;"></div>
</div>
<div class="container" id="container2">
<canvas id="canvas1" style="width: 100%; height: 100%; display: block"></canvas>
</div>
<div class="container" id="container3">
abc
<div style="height: 50pt;"></div>
</div>
</div>
</body>
CSS:
html,
body {
margin: 0;
width: 100%;
height: 100%;
display: flex;
flex-flow: column;
}
p {
margin: 0;
}
#header-navigation {
overflow: hidden; /* FIXME, in the flex environment, this also overflows in width */
flex: 0 1 auto;
}
#page-container {
margin: 5pt;
flex: 1 1 auto; /* part of the column flex layout */
display: flex; /* contains the row flex */
flex-flow: row;
}
.container {
height: 100%;
margin-left: 5pt;
margin-right: 5pt;
}
#container1 {
background: lightgray;
flex: 0 1 15%;
}
#container2 {
flex: 1 1 auto;
}
#container3 {
background: lightgray;
flex: 0 1 15%;
}
#canvas1 {
width: 100%;
height: 100%;
}
My main issue was the shrink value in the flex parameter. It allowed the top bar to vanish on decreasing the page size.
I didn't find a way to solve the issue of the canvas rendering resolution not exactly matching it's actual size, so lines sometimes still blur. Setting the canvas' shrink option to 0 mostly solves this, but the rest of the UI breaks then. I suspect it is caused by the Element actually having a non integer width which isn't represented by the clientWidth/offsetWidth parameters.

Get a Circles Width Defined by a Percent in Pixels and apply them to the Height Value

After 4 hours of trial and errors, I cannot find a way to get the width of the circle that is defined by a percent and convert it to pixels and make the circles height the same size as the width in pixels. Below is what I have right now. (I have tried many variations of this but cannot figure it out) Right now it only works on my screen. I try it on other devices and the height is just not right. This button is created onload.
Example: Circle Width = 12% , the Pixel Value of 12% on a screen is "70px". So somehow make Circle Height = 70px.
<!DOCTYPE html>
<head>
<title>Circle Test</title>
<meta charset='UTF-8'>
<script type="text/javascript" src="/js/fs"></script>
</head>
<body>
</body>
</html>
// Creates a button
var mainButton = document.createElement("button");
mainButton.style.width = "10%";
// Get the Screens Avaliable Width and Get 10% of it and convert it to pixels
var wad = screen.availWidth * .1 + "px";
// Thinking this would return the pixel amount the circle button is, but it only works on the regular screen and not when resized. It also does not work for mobile.
console.log(wad);
mainButton.style.height = wad;
You set the width of your button to 10% of its containing block's width, and the height to 10% of the width of one of
The available area of the rendering surface of the output device, in CSS pixels.
The area of the output device, in CSS pixels.
The area of the viewport, in CSS pixels.
It's very likely that these are measurements of two different things, or that resizing the window will affect one but not the other. Fortunately, CSS has a unit for "percentage of the window's width": vw. Set your button's height and width in vw units, and you don't need any JavaScript:
button {
width: 10vw;
height: 10vw;
border-radius: 50%;
}
<button>Go</button>
If you really meant that the button is 10% as wide as its container, even if its container isn't as wide as the whole window, you can use the padding-bottom technique detailed in this answer. Unlike height, percentages in padding refer to the width of the containing block:
.wrapper {
width: 40%; /* here I use 40% instead of 10% for aesthetics */
padding-bottom: 40%; /* should match width */
position: relative;
}
.wrapper > button {
display: block;
position: absolute;
width: 100%;
top: 0;
bottom: 0;
border-radius: 50%;
}
/* the rest is just to make figuring out
the exact width of the button difficult */
body {
display: flex;
align-items: stretch;
height: 90vh;
}
body > div {
flex: 1 1 0;
background: rebeccapurple;
margin: 0 4px;
}
<div>
<div class="wrapper"><button>Go</button></div>
</div>
<div></div>
<div></div>
And finally, just for completeness, if you really must get the computed width from JavaScript, you can use window.getComputedStyle:
let button = document.querySelector('button');
let width = window
.getComputedStyle(button)
.getPropertyValue('width');
console.log(button.style.height = /* DO NOT DO THIS */ width);
button {
width: 40%;
border-radius: 50%;
}
/* the rest is just to make figuring out
the exact width of the button difficult */
body {
display: flex;
align-items: stretch;
height: 90vh;
}
body > div {
flex: 1 1 0;
background: rebeccapurple;
margin: 0 4px;
}
<div>
<button>Go</button>
</div>
<div></div>
<div></div>
If you try putting that into a resize handler, though, performance will suffer.

How to use Javascript to get a visitors monitor resolution?

I'm building a website for Advanced Web Design and I'm stuck on something. I want the content of the page (contained in a <div>) to be centered. Using CSS I tried this:
body {text-align: center; margin-left: auto; margin-right:auto;}
That didn't seem to work. I know you can detect a browsers resolution using Javascript, and I got to thinking. How would I detect the width, and use that to set the left and right margins of the body? It would be ((resolutionWidth - 800) / 2) to determine the margins that I need (the <div> is 800px wide).
margin: 0 auto is one of the easiest ways to center content on a website. Don't use JS to center the content unless you absolutely have to.
Check out this jsFiddle on how to use margin: 0 auto: http://jsfiddle.net/NfRtV/
Set your width in the same element as your auto margins. So something like this:
#page { width: 800px; margin: 0 auto; }
In some versions of IE, this doesn't work, so wrap a div around this element, giving it text-align: center, and reset in a child div.
Example markup:
<body>
<div id="ie-page-center">
<div id="page">
...
</div>
</div>
</body>
Example CSS:
#ie-page-center { text-align: center; }
#page { width: 800px; margin: 0 auto; text-align: left; }
margin: auto only works if the element has a width. Try specifying the width and retesting your code!
http://jsfiddle.net/ZQjVL/2
body {
width: 500px;
margin: auto;
border: 1px solid black;
text-align: center;
}
You can't detect their monitor resolution, but you can detect the screen width and height. Those are accessible via javascript. Although you obviously shouldn't do it that way because there is a CSS method to do so.
viewportwidth = window.innerWidth;
viewportheight = window.innerHeight;
use this code
<script language="javascript"> alert('Width:' + screen.width+ ' & Height: ' +screen.height); </script>

Categories