Let the highest element control the flex wrapper height - javascript

I'm designing a footer mega menu containing a number of submenus. The content is dynamic. I want to make the largest submenu span a whole column of the wrapper, and therefore also set the height of it.
I've come up with a solution where I'm measuring the height of the blocks and sets the wrapper height to the height of the highest block.
While this works OK - The solution feels a bit naive and something tells me there are more elegant solutions to it. Preferably without javascript. What do you think? I tried with CSS grid but could not get the result I wished for.
nav {
margin: 0 auto;
width: 80%;
display: flex;
flex-direction: column;
align-content: center;
flex-wrap: wrap;
}
The height of the wrapper element is set with Javascript
/*
Set the height of the wrapper to the highest blocks height
*/
function rearrangeBlocks() {
// Grab the whole menu
const section = document.querySelector('nav');
// Grab all the blocks
const allBlocks = document.querySelectorAll('.block');
// Grab the highest block
const longestBlock = Array.from(allBlocks).reduce((longest, current) => {
if (current.getBoundingClientRect().height > longest.getBoundingClientRect().height) {
return current;
} else {
return longest;
}
});
// Set the height of the menu to the same height of the highest menu item
section.style.height = `${longestBlock.offsetHeight}px`;
}
Codepen

Change some CSS
nav {
margin: 0 auto;
width: 80%;
display: flex;
flex-direction: row;
background: red;
}
https://codepen.io/anon/pen/RemZmb

Related

Dynamically flexible width & number items with flex

I have a set of tags, that I want to show in the client. However, sometimes you might have too many tags and you want to show only one row of tags maximized to your body's width without setting a fixed number of columns or item width, and adding a show more button at the end of the tag list with the same style as a tag.
I have achieved this using Javascript in my Angular project by doing the following:
Find out the width of your tags container dynamically, with ViewChild on my content container:
let contentWidth = this.contentContainer.nativeElement.clientWidth;
Calculate the text width of the see more button and use it to calculate the new content width minus see more button width:
Calculating text function does the following:
const canvas = document.createElement('canvas'); // create a canvas
const context = canvas.getContext('2d'); // get the context
context.font = '12px avertastd-bold'; // set up your font and size
And calculate the text width:
const seeMoreButtonWidth = context.measureText(seeMoreButtonText).width;
Create a new array variable 'previewTags' which will hold the tags that are visible when the tags body is in collapsed state, and fill in as many tags as you can by calculating each tag's width with it's content text you receive from the API by checking if the next tag + its padding (static value) fits into the width.
(Not runnable here)
for (const tag of this.data.tags) {
const width = context.measureText(tag).width;
if (contentWidth - (width + this.tagsPadding) > 0) {
previewTags.push({text: tag});
contentWidth -= (width + this.tagsPadding);
} else {
break;
}
}
Push the see more button at the end of previewTags list:
previewTags.push({text: seeMoreButtonText, isButton: true});
And it looks like this in the html:
<ng-container *ngFor="let tag of previewTags">
<div class="tag" [ngClass]="{'see-more-button': tag.isButton}">{{tag.text}}</div>
</ng-container>
Output:
Resize:
As you see, now the tags are flexiable (this code does not include the show more functionality).
After giving you this background and understanding of what I am doing, I would love to ask if this is possible to achieve with css or less JavaScript intervation?
Something like this could be a pure css solution if your tags have a constant height. I just let the flex-list wrap around and then don't show the overlap.
.content_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: rows;
}
.tag_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: rows;
flex-wrap: wrap;
width: 80%;
height: 32px;
overflow: hidden;
}
.tag_wrapper div {
width:100px;
height:30px;
border: 1px solid black;
}
button {
flex-grow: 4;
}
<div class="content_wrapper">
<div class=tag_wrapper>
<div>Tag1</div>
<div>Tag2</div>
<div>Tag3</div>
<div>Tag4</div>
<div>Tag5</div>
<div>Tag6</div>
<div>Tag7</div>
<div>Tag8</div>
<div>Tag9</div>
</div>
<button>See more</button>
You could probably make the "See more" button solution more elegant, to not have as much white space but I'll leave that to you :)
Here is some javascript to remove the see-more button if it's not needed.
(OBS) this only works if all the tags are the exact same width and have the same margin. I did this to avoid looping through all values and checking their width individually.
(I know the list is in the wrong order, I made it like that to get the see-more button fit in well without having to tinker a bunch.
function getWidthWithMargin(elem) {
var style = elem.currentStyle || window.getComputedStyle(elem)
margin = parseFloat(style.marginLeft) + parseFloat(style.marginRight)
return(elem.getBoundingClientRect().width + margin)
}
function handleWindowSizeChange() {
let tags = document.getElementsByClassName("tag");
if(tags.length != 0)
{
let tag_width = getWidthWithMargin(tags[0]);
if(tags[0].parentElement.getBoundingClientRect().width/tag_width > tags.length) {
document.getElementById("see-more-button").style.display = "none";
}
else{
document.getElementById("see-more-button").style.display = "block";
}
}
}
window.onload = handleWindowSizeChange;
window.onresize = handleWindowSizeChange;
.content_wrapper {
}
.tag_wrapper {
display: flex;
justify-content: flex-start;
flex-direction: row-reverse;
flex-wrap: wrap;
width: 100%;
height: 32px;
overflow: hidden;
}
.tag_wrapper div {
min-width:100px;
height:30px;
border: 1px solid black;
margin: 10px;
}
.tag_wrapper button {
height:30px;
flex-grow: 50;
}
<div class="content_wrapper">
<div class=tag_wrapper>
<button id="see-more-button">See more</button>
<div class="tag">Tag1</div>
<div class="tag">Tag2</div>
<div class="tag">Tag3</div>
<div class="tag">Tag4</div>
<div class="tag">Tag5</div>
<div class="tag">Tag6</div>
<div class="tag">Tag7</div>
<div class="tag">Tag8</div>
</div>

Chat box scroll down new messages

I want that when the user opens that chat or writes any message, the scroll bar to go down to see the latest messages. I have found the following answer that I want to use in order to accomplish the task.
https://stackoverflow.com/a/21067431/12051965
The problem is that it does not have any effect on the scroll bar, it is still at the top of the chatbox, and I would appreciate if someone could tell me what am I doing wrong.
let chat = document.getElementById("chat-messages-main-div-id");
window.onload = toBottom;
function toBottom() {
const isScrolledToBottom = chat.scrollHeight - chat.clientHeight <= chat.scrollTop + 1;
if (isScrolledToBottom) {
chat.scrollTop = chat.scrollHeight - chat.clientHeight;
}
}
.chat-messages-main-div {
width: 100%;
height: inherit;
overflow: overlay;
overflow-y: scroll;
display: flex;
flex-direction: column;
padding-left: 2%;
padding-right: 2%;
box-sizing: border-box;
padding-bottom: 15px;
}
<div id="chat-messages-main-div-id" class="chat-messages-main-div" onload="toBottom">
....
</div>
There is two issues with your code snippet the first one comes from the height: inherit, which make your div grow with parent element, so the scroll bar you are seeing is a parent node (the first fixed height parent if any or the window object) scrollbar and not the chat one, the div or its parent have to be limited in height for it to work, also your comparaison in the toBottom function should be a >= instead of <= (The scrollTop property is the number of pixel scrolled from the top), but i recommend you something easier (you dont need to check or calculate the position if its given that all you need to is to go to the upmost bottom of the scroll) :
function toBottom() {
chat.scroll(0, chat.scrollHeight)
}

carousel, let horizontal scrolling end movement when parent element vertical scrolling is moved

I created a carousel similar to the one on Instagram that is working, but I realized that if I move the scroll y of the parent element before the scroll x movement of the child element ends it will not end.
gif of the example below ...
note: sorry for bad English, not my native language.
code
.container {
width: 100%;
display: flex;
flex-direction: column;
overflow-y: scroll;
.x {
margin-top: 30px;
display: flex;
overflow-x: scroll;
scroll-snap-type: x mandatory;
div {
flex: 0 0 auto;
scroll-snap-align: start;
width: 100vw;
height: 100vw;
}
.x-item1 {
background: green;
}
.x-item2 {
background: blue;
}
}
}
<Container>
<div className="x">
<div className="x-item1" />
<div className="x-item2" />
</div>
... other divs
</Container>
--->> Example Gif !! <<---
I found a solution that, in my opinion, meets my proposal without losing the performance of the functionality ...
I just thought that there could be a delay in scrollIntoView, like "behavior: smooth" ...
https://developer.mozilla.org/pt-BR/docs/Web/API/Element/scrollIntoView
but it didn't work if someone wants to add an improvement, thanks.
in the container I put an "onTouchStart"
<Container className="works" onTouchStart={() => handleCarousel()} >
<div className="x">
<div className="x-item1" />
<div className="x-item2" />
</div>
... other divs
</Container>
function handleCarousel...
const handleCarousel = () => {
const works = Array.from(document.querySelectorAll('.work-content'));
const carousels = Array.from(document.querySelectorAll('.work-carousel'));
carousels.forEach(carousel => {
if (carousel.scrollLeft % window.innerWidth !== 0) {
const item = document.querySelector(
`.work-content:nth-child(${
works.indexOf(carousel.parentElement) + 1
}) > div.work-carousel > img.work-carousel-item-active`,
); // in another function I already control which image is being viewed and add this class
const scroll = document.querySelector('.works').scrollTop; // optional: get vertical scroll value
item.scrollIntoView(); // horizontal scrolling gains focus
document.querySelector('.works').scrollTop = scroll; // optional: returns vertical scroll value
}
});
};
I believe this is the expected behavior; scrolling in one direction interrupts scrolling in the other, even scrolling that's declared manditory by Scroll Snap CSS attributes.
If you dislike this behavior, you might use a JS-based animation that programmatically scrolls the container horizontally, since this won't be affected by vertical scrolling.
Sorry I can't help more.

Adjust page function, when changing device, just reloading works

I have a function to determine the height of the page, so that the page always stays at the maximum size regardless of the device, without scrolling.
I first take the maximum height of let s = $ (document) .height and then the height of all other elements, such as the header, main footer and footer. I subtract the value of all items by the variable s, which contains the height total. I assign the result to the main height value, so the page is the way I want it.
However, when I change the device to chrome inspection feature, or I leave it in landscape, the page is irregular. So be sure to reload, try using windows.resize by calling a function, but it doesn't adjust, just reloads. I don't know what to do.
I call the function like this:
$("document").ready(function() {
changesize();
$(window).resize(function() {
changesize();
});
};
Any reason you couldn't use css to accomplish this? height: 100vh will keep an element at the viewport height even when resized, and using flex or grid you could stretch and scale the main layout elements however you need them.
body {
margin: 0;
text-align: center;
}
.container {
height: 100vh;
display: flex;
flex-direction: column;
}
header, footer {
background: aliceblue;
flex: 0 0 auto;
}
.content {
flex: 1 1 auto;
background: lightblue;
}
<section class="container">
<header><h1>header</h1></header>
<div class="content">content</div>
<footer>Footer</footer>
</section>

How to create two columns without blocking horizontal rows [duplicate]

This question already has an answer here:
How to reorder divs using flex box?
(1 answer)
Closed 2 years ago.
I have got a Wordpress loop of posts. This outputs some kind of post-list. To make it easy, we can consider it a ordered-list like that:
<ol>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ol>
Each list item got a unique, different height. When a certain device width is given, I want them to display side by side, without a "row like behavior". So each column should place the next post right below it, like illustrated below (no unnecessary white space below the shorter items):
Using float, flex-box and css-grid or display: inline-block did not work for me.
Although I would love to avoid two loops with the same content inside my DOM, as its a bad behavior for screen-readers etc.
Is there a solution I do not see without a lot of javascript? The internet is full of float: left; examples searching for "two columns", "flexible columns" and I did not find anything helpful.
You can use display: flex and flex-direction: column;. By adding a height (or max-height) to the parent container, you make the elements automatically go to next column. Then you can change order attribute of some element to push them into the second row.
This solution is not very generic as it will depend on the content, but it may give an idea on how you can do it.
$('li').each(function() {
$(this).css('height',Math.floor((Math.random() * 50) + 30)+"px");
})
ol {
list-style: none;
display: flex;
flex-wrap: wrap;
flex-direction: column;
max-height: 100vh;
margin:0;
padding:0;
}
li {
box-sizing: border-box;
text-align:center;
padding: 10px;
background: red;
max-width: 50%;
margin: 5px;
}
li:nth-child(2n) {
background:green;
order:1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ol>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ol>
Important notice (added by #Marian Rick):
This approach does only work if the left column is bigger than the right one
You need to set a fixed height, which does not allow dynamic content
Both of these problems can be solved using a javascript snippet to keep the solution dynamic.
I have another fancy answer. It uses flex-direction:coulmn and the page-break-before to force every second elemend in the second column. This way you have no restriction to the height of the full list.
Please check the jsfiddle in a separate tab to check how I used the breakpoint to toggle from normal listing to two coulmns.
Also check if it runs in all targeted browser: https://caniuse.com/#search=page-break-before
section {
display: flex;
flex-wrap: wrap;
}
article {
box-sizing: border-box;
border:1px solid grey;
width: 100%;
height: 50px;
}
#media (min-width: 500px) {
section {
flex-direction: column;
}
article {
width: 50%;
}
article:nth-child(even) {
order: 2;
}
article:nth-child(2) {
/* this breaks into the second column after the 2nd child
(which is not the first element of the second half of elements) */
page-break-before: always;
}
}
/* just for demo */
article:first-child {
height: 66px;
background-color: #e0e0fe;
}
article:nth-child(4) {
height: 80px;
background-color: #aee0e0;
}
article:nth-child(6) {
height: 130px;
background-color: yellow;
}
<section>
<article>1</article>
<article>2</article>
<article>3</article>
<article>4</article>
<article>5</article>
<article>6</article>
<article>7</article>
</section>
Based on the great idea of #TemaniAfif, I have written a small, barely tested jQuery snippet, that achieves the following:
Each item will be placed as close as possible to the top, regarding to its position inside the container
While resizing the browser, each item updates its position
Its very few and fast javascript, while CSS does most of the work
The whole concept is still based on the idea of pushing items either to the left or right side, using the order: x attribute.
There is a CODEPEN DEMO to play around with.
Notice: Browser support is equal to the browser support of flex-box.
"use strict";
// DEMO STYLE - Should be removed
// calculate random heights for each item
$("li").each(function() {
$(this).css("height", Math.floor(Math.random() * 300 + 2) + "px");
});
///////////////////////
// Calculate columns
//
// 1. loop through each item.
// 2. first get the height of item
// 3. than check which column is shorter
// 4. if left column is shorter or equal, keep item on the left side
// 5. if right column is shorter, push this item to the right side
// 6. check which side will be higher
// 7. if left column is higher, assign height of column to parent container
// 8. if right column is higher, create a margin-bottom equal of the column offset and assign it to the left column
// calculation is finished. test it.
// finally add the height of the bigger column to the div
// if its the left column, assign the height of the right
var container = $("ol");
var items = container.find("li");
var breakPoint = 768; // if equal or bigger, the calculation will be fired
var calcPositions = function calcPositions() {
// quit function if its a mobile device
if ($(window).width() < breakPoint) return;
// reset margin of left column item
container.find("li.push-left").last().css("margin-bottom", "15px");
var leftColumnHeight = 0;
var rightColumnHeight = 0;
// 1. loop through each item
items.each(function(i, e) {
// 2. get height of item
var height = $(this).outerHeight(true);
// 3. check which column is shorter
if (leftColumnHeight <= rightColumnHeight) {
// 4. if left column is shorter or equal, keep item on the left side
leftColumnHeight += height;
$(this).removeClass("push-right").addClass("push-left");
return; // skip rest and continue with next item
}
// 5. if right column is shorter, push this item to the right side
// using .push-right { order: 5 } inside css
rightColumnHeight += height;
$(this).removeClass("push-left").addClass("push-right");
});
// 6. check which side will be higher
if (leftColumnHeight >= rightColumnHeight) {
// 7. if left column is higher, assign height of column to parent container
container.height(leftColumnHeight);
return; // end of function
}
// 8. if right column is higher, create a margin-bottom equal of the column offset and assign it to the left column
// otherwhise the second object can be displayed at the bottom of the left column
// get offset of columns
var columnOffset = rightColumnHeight - leftColumnHeight;
// assign offset to last element of left sidebar
container.find("li.push-left").last().css("margin-bottom", columnOffset + "px");
// assign height to container
container.height(rightColumnHeight);
};
// calculate initially
calcPositions();
// calculate on resize
$(window).resize(function() {
calcPositions();
});
/* functional classes needed for this grid */
/* keep this breakpoint in sync with "breakPoint" inside the javascript */
#media (min-width: 768px) {
ol {
display: flex;
flex-direction: column;
flex-wrap: wrap;
}
li {
max-width: 50%;
}
li.push-right {
order: 1;
margin-right: 0;
}
}
/* demo styles that can be removed */
* {
box-sizing: border-box;
}
ol {
padding: 0;
max-width: 800px;
width: 90%;
margin: 0 auto;
}
li {
background: red;
margin-bottom: 15px;
}
#media (min-width: 768px) {
li {
max-width: 49%;
margin-right: 2%;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Breakpoint is set to >=768px for two columns:</p>
<ol>
<li>Lorem.</li>
<li>Asperiores!</li>
<li>Illum!</li>
<li>Perspiciatis!</li>
<li>Eius.</li>
<li>Est.</li>
<li>Quisquam.</li>
<li>Eaque!</li>
<li>Vero?</li>
<li>Iste?</li>
<li>Provident?</li>
<li>Ipsum.</li>
</ol>
You can do this with inline-block if you want you avoid flex for some reason. Just use media queries when you to have all items in one column.
div {
width: 95%;
margin: auto;
}
li {
background: red;
width: 49%;
min-height: 300px;
display: inline-block;
text-align: center;
font-size: 50px;
margin-top: 10px;
}
#media screen and (max-width: 600px) {
li {
width: 98%;
}
}
.one {
background: blue;
}
.three {
background: green;
}
<div>
<ol>
<li class='one'>1</li>
<li>2</li>
<li class='three'>3</li>
<li>4</li>
<li>5</li>
</ol>
</div>

Categories

Resources