Prevent "widows" - items alone in a row - when using css grid - javascript

I created a responsive grid which should contain rows of 4, 3, 2 or 1 items, depending on the window size. Hard requirement from Design is that no widows and no half filled rows are allowed. So for 11 items, there could be two rows of four items and one of three, or three rows of three items and one of two items, or... . You get the idea.
To solve the problem, I created a css grid:
display: grid;
grid-template-columns: repeat(auto-fit, minmax(264px, 1fr));
grid-gap: 24px;
This works fine for the resizing, but it does not take care of the widow problem. At first I did the naive approach of selecting the last item with a last-of-type selector and giving it a grid-row: 1 / -1 style, but that does not ensure that the row above is fully filled.
I guess there won't be a css only solution. Is there a way for an element to realize that it is alone in a row, or that it has to grow to fill the row? I had an idea using the nextSibling property to select the last child via JavaScript and maybe determine via the page offset if it fills the whole row. But my problem is that I can't hardcode the width of the screen.
Alternatively, is there a way to tell an element to spread until the end of the row? I tried this, but it did not work:
.card:last-of-type {
grid-column-end: span -1;
}
Click here for minimal reproducible example.

A solution is to use flexbox instead of grid. This way it can stretch with the screen size.
We use 25% for a 4 column layout. Subtracting 1rem for a bit of margin. (0.5 left + 0.5 right)
(Open snippet in fullscreen to see it working)
.my-grid {
display: flex;
flex-wrap: wrap;
}
.card {
min-width: 264px;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 1px 1px #00000026;
font-size: 14px;
background-color: red;
margin: .5rem;
flex: 1 1 calc(25% - 1rem);
}
<div class="my-grid">
<div class="card">1</div>
<div class="card">1</div>
<div class="card">1</div>
<div class="card">1</div>
<div class="card">1</div>
<div class="card">1</div>
<div class="card">1</div>
<div class="card">1</div>
<div class="card">1</div>
<div class="card">blub</div>
</div>

Related

Animate elements in and out of page

I have several images that I need to horizontally cross the page to the right, exit the page and then re-enter the page from the left. Some of the images will already be out of view, so they will have to enter first.
This is a sketch of what I've tried so far:
var elems = document.getElementsByClassName("child");
for (const elem of elems) {
elem.animate(
[
// keyframes
{transform: "translateX(300px)"},
],
{
// timing options
duration: 5000,
iterations: Infinity
},
);
}
.container {
background-color: aqua;
width: 1000px;
display: flex;
flex-direction: row;
overflow:hidden;
padding: 20px 0;
gap: 10px;
}
.child {
background-color: red;
flex: 0 0 20%;
}
<div class="container">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
<div class="child">4</div>
<div class="child">5</div>
<div class="child">6</div>
<div class="child">7</div>
<div class="child">8</div>
</div>
For start I tried to slide out all the divs, but even that I don't understand why is not working.
I'm using your code as a starting point however there are 2 major differences between my code and yours. The first is that this solution is not using JavaScript, which is a plus, but it may not be what you are looking for. The second difference is that rather of animating the div elements with the class child, this solution is animating a wrapper div with the class slider.
One important thing to note, is that some calculations must be used for the animation to work properly. Adding or removing elements will require that the values are updated. The formula is the following:
Child div size: 20% (CHILD_SIZE)
Gap between children divs: 10px (GAP)
Amount of the children: 8 (CHILDREN_AMOUNT)
So together it goes like this: translateX(calc((CHILD_SIZE - GAP) * CHILDREN_AMOUNT));
var slider = document.getElementsByClassName('slider')[0];
slider.innerHTML += slider.innerHTML;
.container {
background-color: aqua;
width: 100%;
overflow: hidden;
}
.slider {
display: flex;
flex-direction: row;
padding: 20px 0;
gap: 10px;
animation: slideRight 10s infinite linear;
}
.child {
background-color: red;
flex: 0 0 20%;
}
#keyframes slideRight {
from {
transform: translateX(calc((-20% - 10px) * 8));
}
to {
transform: translateX(100% + 10px);
}
}
<div class="container">
<div class="slider">
<div class="child">1</div>
<div class="child">2</div>
<div class="child">3</div>
<div class="child">4</div>
<div class="child">5</div>
<div class="child">6</div>
<div class="child">7</div>
<div class="child">8</div>
</div>
</div>
Updated considering the comment:
There are a few ways, the simpler way though is just to duplicate the div.child elements without touching the animation formula. This can be done just in the markup or using JavaScript to have a more dynamic solution (I have updated the code above to have the desired result).
What I consider a better way, though (not going to elaborate here as many libraries already solve this problem, just search for carousel js libraries), is to just prepend and append the necessary amount of elements to have the desired result instead of duplicating all of them.

How to bring elements closer together as the screen narrows?

I am using negative margin to cause some playing card elements to overlap, creating a visual effect akin to how a hand of playing cards appear in real life.
The issue here is that the negative margin is defined in vw. This means that as the screen size widens, the value of a single vw becomes greater, and the negative margin grows, causing the cards to overlap more. This is contradictory to my intentions-- as the screen widens, the cards should spread accordingly, and overlap more as the screen narrows.
If I instead use an absolute value like px, the overlap will remain constant as the screen size changes, which is also undesirable.
I have spent many hours trial & erroring this and have yet to come up with a scheme that works correctly.
How can you cause a negative margin to diminish as the screen increases, with CSS or with JS if CSS can't do it alone?
Codepen
You could combine clamp() with relative positioning. You'll probably need to adjust the values a little to make it good looking, but this is the core concept.
:root {
--cardWidth: 200px;
--min: calc(var(--cardWidth) / 8);
--var: 7.5vw;
--max: calc((var(--cardWidth) / 4) * 7);
--overlap: clamp(
var(--min),
var(--var),
var(--max)
);
}
body {
display: grid;
place-items: center;
margin: 0;
}
.w {
display: flex;
}
[class*=card] {
height: 300px;
width: var(--cardWidth);
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px -1px #000;
}
.card-2 {
position: relative;
left: calc((var(--cardWidth) * -1) + var(--overlap));
}
<div class="c"></div>
<div class="w">
<div class="card"></div>
<div class="card-2"></div>
</div>
You can just use flex box for this. Try out https://css-tricks.com/snippets/css/a-guide-to-flexbox/ .

Animate shrink and expand transitions for subsets of elements in a nested set of divs in javascript [duplicate]

This question already has answers here:
How can I transition height: 0; to height: auto; using CSS?
(41 answers)
Closed 5 years ago.
I need to animate the shrink and expand transitions for subsets of elements in a nested set of divs. The expand works fine, but the shrink is broken.
I have a structured collection of items (say, squares for purposes here) that I want to display selectively and whose transitions I want to animate.
The structure is: collection > groups > rows > squares
In static layout,
squares appear in horizontal rows;
rows are gathered vertically in groups;
a column of groups forms the collection.
Squares can be of varying sizes, so containers must adjust accordingly
(no fixed heights or widths).
I have obtained a static layout that is just what I want.
The problem comes with animation. I want to hide various subsets
of squares, rows and/or groups. Nearby items can look similar so it is
difficult for users to tell just what is being added or subtracted, so
I need a smooth animation so users can follow what is changing.
I am trying to do this with a CSS-animated shrink:
A shrinkMe class marks all element that I will want to
shrink/expand at the moment
CSS transition times are set for these
shrinkMe elements
A shrunken class is defined whose CSS has all its
size parameters set to 0
To shrink or expand, jQuery adds or removes
the the shrunken class tag to the $('shrinkMe') items, to animate
items between the full and shrunken (=0) sizes
The un-shrink animated transition is exactly what I want. But the shrink animation does not work at all - contents spill out of containers along the way.
shrink = function(bool,nsec) {
$('.shrinkMe').css("transition", 'all ' + nsec+ 's');
if (bool) $('.shrinkMe').addClass('shrunk')
else $('.shrinkMe').removeClass('shrunk');
}
anim = function(secs) {
return 'all ' + secs + 's'
}
.controls {
display: inline-block;
vertical-align: top;
}
.collection {
display: inline;
text-align: center;
border: 2px solid black;
padding: 3px;
display: inline-block;
}
.group {
display: block;
margin: 2px;
border: 2px solid black;
padding: 3px;
}
.row {
display: block;
vertical-align: middle;
background: #0cc;
margin: 1px;
border: 2px solid black;
}
.sq {
display: inline-block;
background: white;
width: 20px;
height: 20px;
margin: 5px;
border: 2px solid black;
}
.lg {
width: 25px;
height: 25px;
}
.shrinkMe {
border-color: red;
}
.shrunk {
height: 0px;
width: 0px;
border-width: 0px;
padding: 0px;
margin: 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.2.3/jquery.min.js"></script>
<div class='controls'>
<button type='button' onclick='shrink(true,2)'>shrink reds </button>
<br>
<button type='button' onclick='shrink(false,2)'>unshrink reds </button>
</div>
<div class='collection'>
<div class='group'>
<div class='row '>
<div class='sq'></div>
<div class='sq'></div>
</div>
<div class='row shrinkMe'>
<div class='sq lg shrinkMe'></div>
<div class='sq shrinkMe'></div>
<div class='sq lg shrinkMe'></div>
</div>
</div>
<div class='group shrinkMe' id='group2'>
<div class='row shrinkMe' id='group2container2'>
<div class='sq shrinkMe'></div>
<div class='sq shrinkMe'></div>
</div>
<div class='row shrinkMe'>
<div class='sq shrinkMe'></div>
<div class='sq lg shrinkMe'></div>
<div class='sq shrinkMe'></div>
</div>
</div>
</div>
Among other things, I've tried using different explicit transition speeds, so containers shrink more slowly than their contents, but to no avail. If I set explicit heights and widths of the rows and groups, I can get a coordinated nested-shrink but it is ugly:
It shrinks by squashing to the left side and;
The final layouts can have empty spaces in them (e.g., when a row shrinks inside an unshrunk group, the fixed explicit group container height means there is now a hole where the row was).
What I want is to achieve is simple: a shrink animation that is the time-reverse of the nice clean un-shrink.
Here also is a jsfiddle showing the problem that additionally has buttons for trying separate timings of more deeply nested divs (which
does not help...)
https://jsfiddle.net/furnas/dgq8yusy/
Any explanation of what is going wrong and suggestions on how to fix
it?
Use jquery anymation onComplete event for adding and removing classes to divs.
because your css transition works like asyncronous method on another thread, so, you need to wait for it to complete and than add/remove classes.
http://api.jquery.com/animate/

Keep the same flex-growth between lines [duplicate]

My problem is that I want the flexbox with variable range width, and all works well, but not on the last row. I want the same dimension for all children even where the row is not full of children (the last row).
#products-list {
position:relative;
display: flex;
flex-flow: row wrap;
width:100%;
}
#products-list .product {
min-width:150px;
max-width:250px;
margin:10px 10px 20px 10px;
flex:1;
}
I created a dynamic situation in jsFiddle
My flex divs can shrink until 150px and grow up to 250px, but all must be with the same size (and obviously I want a CSS solution, with JS I know the way).
Unfortunately, in the current iteration of flexbox (Level 1), there is no clean way to solve the last-row alignment problem. It's a common problem.
It would be useful to have a flex property along the lines of:
last-row
last-column
only-child-in-a-row
alone-in-a-column
This problem does appear to be a high priority for Flexbox Level 2:
CSS Working Group Wiki - Specification Issues and Planning
https://lists.w3.org/Archives/Public/www-style/2015Jan/0150.html
Although this behavior is difficult to achieve in flexbox, it's simple and easy in CSS Grid Layout:
Equal width flex items even after they wrap
In case Grid is not an option, here's a list of similar questions containing various flexbox hacks:
Properly sizing and aligning the flex item(s) on the last row
Flex-box: Align last row to grid
Flexbox wrap - different alignment for last row
How can a flex item keep the same dimensions when it is forced to a new row?
Selector for an element alone in a row?
Aligning elements in last flexbox row
How can I allow flex-items to grow while keeping the same size?
Left-align last row of flexbox using space-between and margins
Inconsistent margin between flex items on last row
How to keep wrapped flex-items the same width as the elements on the previous row?
How to align left last row/line in multiple line flexbox
Last children of grid get giant gutter cause of flexbox space-between
Managing justify-content: space-between on last row
Flexbox space between behavior combined with wrap
Possible to use CSS Flexbox to stretch elements on every row while maintaining consistent widths?
As a quick and dirty solution one can use:
.my-flex-child:last-child/*.product:last-child*/ {
flex-grow: 100;/*Or any number big enough*/
}
You could try using grid instead of flexbox here:
#products-list {
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(auto-fit, minmax(100px, 250px)); //grid automagic
justify-content: start; //start left
}
Fiddle link
There is a great solution that works always.
add a div with class product (The same class for other items that are under flex) and add a style for this div:height:0px;
you need to add as many dives that are possible to be in one row.
<div class="product" style="height:0px">
as many that can be in one row.
That's all. Works always.
If all your rows have the same number of items, you can use :nth-last-child. For example, if all the rows have 3 items, you can do something like this to remove the margin of the last 3 items:
.container{
display: flex;
flex-wrap: wrap;
background: yellow;
}
.item{
width: calc((100% - 2*10px)/3);
height: 50px;
background: blue;
color: white;
margin-right: 10px;
margin-bottom: 10px;
padding: 5px;
box-sizing: border-box;
}
/* last item of each row */
.item:nth-child(3n){
margin-right: 0;
font-size: 150%;
}
/* last 3 items */
.item:nth-last-child(-n+3){
margin-bottom: 0;
background: green;
}
<div class="container">
<div class="item" >1</div>
<div class="item" >2</div>
<div class="item" >3</div>
<div class="item" >4</div>
<div class="item" >5</div>
<div class="item" >6</div>
<div class="item" >7</div>
</div>
A simple trick adds a flexible space to fill the rest of the last row:
#products-list{
display:flex;
flex-flow: row wrap;
justify-content:space-between;
}
#products-list::after {
content: "";
flex: auto;
flex-basis: 200px;/*your item width*/
flex-grow: 0;
}
But you shouldn't use margins on items then. Rather wrap them into containers with padding.
I used this workaround, even if it's not very elegant and it doesn't use the power of Flexbox.
It can be carried out on the following conditions:
All the items have the same width
The items have a fixed width
You use SCSS/SASS (can be avoided though)
If this is the case, you can use the following snippet:
$itemWidth: 400px;
$itemMargin: 10px;
html, body {
margin: 0;
padding: 0;
}
.flex-container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin: 0 auto;
border: solid 1px blue;
}
#for $i from 1 through 10 {
#media only screen and (min-width: $i * $itemWidth + 2 * $i * $itemMargin) {
.flex-container {
width: $i * $itemWidth + 2 * $i * $itemMargin;
}
}
}
.item {
flex: 0 0 $itemWidth;
height: 100px;
margin: $itemMargin;
background: red;
}
<div class="flex-container">
<div class="item"></div>
<div class="item" style="flex: 500 0 200px"></div>
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
Here I have created an example on codepen which also implements margin.
The second and the third conditions can be avoided respectively using css variables (if you decided to provide support for it) and compiling the above scss snippet.
Well, it's true, we could do it also before flexbox, but display: flex can be still essential for a responsive design.
I was facing this same issue where I wanted to have a variable number of items in a resizable container.
I wanted to use all of the horizontal space, but have all of the flex items at the same size.
I ultimately came up with a javascript approach that dynamically added padding spacers as the container was resized.
function padLastFormRow() {
let topList = [];
let nSpacersToAdd = 0;
$('#flexContainer').find('.formSpacer').remove();
$('#flexContainer').find('.formItem').each(function(i, formItem) {
topList.push($(formItem).position().top);
});
let allRowLengths = getFlexLineLengths(topList);
let firstRowLength = allRowLengths[0];
let lastRowLength = allRowLengths[((allRowLengths.length) - 1)];
if (lastRowLength < firstRowLength) {
nSpacersToAdd = firstRowLength - lastRowLength ;
}
for (var i = 1; i <= nSpacersToAdd; i ++) {
$('#flexContainer').append(formSpacerItem);
}
}
Please see my Fiddle:
http://jsfiddle.net/Harold_Buchman/z5r3ogye/11/
I was having a similar challenge with menu rows. I wanted more spacing on the top of the second row of menu items.
The use of flex-box's row-gap worked well.
https://developer.mozilla.org/en-US/docs/Web/CSS/row-gap
.menu {
display: flex;
flex-wrap: wrap;
row-gap: 10px;
}
This added a margin-top type effect to menu items were wrapped to the second line.
If all your rows have the same number of items, you can use :nth-last-child. For example, if all the rows have 3 items, you can do something like this:
.container{
display: flex;
flex-wrap: wrap;
background: yellow;
}
.item{
width: calc((100% - 2*10px)/3);
height: 50px;
background: blue;
color: white;
margin-right: 10px;
margin-bottom: 10px;
padding: 5px;
box-sizing: border-box;
}
// last item of each row
.item:nth-child(3n){
margin-right: 0;
background: green;
}
// last 3 items
.item:nth-last-child(-n+3){
margin-bottom: 0;
font-size: 150%;
}
<div class="container">
<div class="item" >1</div>
<div class="item" >2</div>
<div class="item" >3</div>
<div class="item" >4</div>
<div class="item" >5</div>
<div class="item" >6</div>
<div class="item" >7</div>
</div>

Marquee-like horizontal scroll without scrollbars?

I have seen both overflow scrolling with no scrollbars and Hide scrollbar for mobile devices while keeping the scroll ability ; unfortunately, both of these suggest a solution with position: absolute; and I think that I cannot really apply that to my problem.
The code in this example renders this:
Basically, the red outline boxes are divs of class .myBox, with a fixed width. These divs are side-by-side, in a container that is horizontally centered inside its container div. The top part is reserved for titles, which may be long. I'd like the titles to be rendered as on the right side one - but if they have focus, then I'd want the titles to scroll left-right with either keyboard arrow buttons, mouse wheel - or (also for mobile) dragging left and right.
Of course, since the right box's title has overflow: hidden;, there is no possibility to scroll. If I leave the scrollbar visible (overflow-x: scroll;) as on the left box, then the title is not visible at all (and I cannot scroll anyways).
So is it possible somehow to allow scrolling in the title parts of these boxes in this way (sort of like a marquee scroll behavior, but manual)?
Bonus question: is there a sort of a JavaScript library (or even better, a plain CSS solution), that would allow something similar - except, if the text is too long, it is truncated and ellipsis is added (so, instead of "My even longer" it should show "My even lon..." at start), then as you drag right to left, it also calculates ellipsis at start and at end - and when you come to the right end, it takes away the right ellipsis?
The example code is:
.mainHolder {
font-size: 14px;
border: 2px solid #999;
text-align: center; /* centers the span */
}
.centerer {
border: 2px solid #555;
display: inline-block; /* makes the span have the same width as its div contents*/
margin: 0;
padding: 0;
}
.myBox {
width: 8em;
border: 2px solid #F55;
border-radius: 0.5em;
display: inline-block; /* enables two myBox side-by-side */
}
.heading {
height: 1.25em;
border-radius: 0.25em;
background-color: #94B6F7;
overflow: hidden;
overflow-x: scroll;
}
/*just as example, remove the scroller of box2*/
#box2 .heading {
overflow-x: hidden;
}
<div class="mainHolder">
<span class="centerer">
<div id="box1" class="myBox">
<div class="heading">
My very long title
</div>
<div class="data">
Data: 1
</div>
</div>
<div id="box2" class="myBox">
<div class="heading">
My even longer title
</div>
<div class="data">
Data: 2
</div>
</div>
</span>
</div>

Categories

Resources