Using box model to contain items within its parent div - css

Using the following HTML and CSS3 rules, I'm trying to make sure that the following criteria are adhered to:
I have all of the criteria working except for item 1 where the
children are exceeding their parent's width. Question: How to keep children within their parent?
li items cannot exceed their parent width i.e. 400px
img, label, and currency content must be centred vertically within their span
currency should not wrap and should always be displayed in full
currency should always be displayed as close as possible to the
label span.
the label text should be clamped at 2 lines with ellipsis displayed where it exceeds 2 lines.
Note: Only needs to work in Chrome and Safari webkit-based browsers.
It should look like:
However, it looks like this at the moment:
Any ideas?
********************* JS Fiddle example ************************
<ul>
<li>
<span class="img"></span>
<span class="label">Acclaim</span>
<span class="currency">(USD 50)</span>
</li>
<li>
<span class="img"></span>
<span class="label">Acclaim 1 11 111 1111 11111</span>
<span class="currency">(USD 50)</span>
</li>
<li>
<span class="img"></span>
<span class="label">Acclaim 1 11 111 1111 11111 2 22222 2222 22222 3 33 333 3333 33333 4 44 444 4444 44444 5 55 555 5555 55555 6 66 666 6666 66666</span>
<span class="currency">(USD 50)</span>
</li>
</ul>
ul {
width: 400px;
background-color: yellow;
padding: 0;
margin: 0;
}
li {
display: -webkit-box;
padding-right: 50px;
}
.label {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-box-pack: center;
-webkit-line-clamp: 2;
text-overflow: ellipsis;
overflow: hidden;
white-space: normal;
word-wrap: break-word;
line-height: 1.3em;
margin-right: 0.2em;
background-color: pink;
}
.currency {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-box-pack: center;
white-space: nowrap;
background-color: lightgreen;
}
.img {
display: block;
width: 40px;
height: 40px;
margin-right: 0.1em;
background-image:url('data:image/png;base64,iVBOR...);
}

The following will work, providing the price doesn't get wider:
http://jsfiddle.net/WDjZ3/4/
I simply converted the margin-right to pixels so I knew the space taken up by the content other than the label, then added a max-width for the remainder:
.label {
margin-right: 3px;
max-width: 294px;
}
if the price or image are variable width, you will need js to read their widths, remove from 400px and add that value to max-width.
Here is an example using JavaScript to dynamically set the max-width: http://jsfiddle.net/WDjZ3/7/
I haven't checked all dimensions, so that should be added to make it fully accurate, otherwise the margins will make the price no longer fit.
I basically checked the width of the other elements:
function getWidth(el) {
return parseInt(window.getComputedStyle(el,null).getPropertyValue("width"));
}
I then removed this width from 400px, and set the maxWidth:
for (var i=0; i<imgs.length; i++) {
var width = 400 - (getWidth(imgs[i]) + getWidth(currencies[i])) + "px";
labels[i].style.maxWidth = width;
}

I’ve solved this to work without JavaScript. However, it only works in more modern browsers, including Chrome and Opera. It will not work in Safari. It will work in Firefox once it updates to the latest spec. I can probably get it to work in IE10, but I have to look into that more. The ellipsis only works in Chrome/Safari as it is vendor specific.
The demo is at http://jsfiddle.net/uLm92/1/
First of all we want to make the li as a flexbox container and set it to 400px, so the flex items will try to fit that size (more on that later):
li {
/* make all items inside li as flex items */
display: -webkit-flex;
display: flex;
height: 40px;
max-width: 400px;
}
Now the child items are flex items, the key to getting the label to fit is setting the image and the price to not flex. For the price, we know it wants to always be 40xp wide, so I set the following:
.img {
width: 40px;
height: 40px;
-webkit-flex-basis: 40px;
flex-basis: 40px;
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
This says that the preferred with id 40px and never make it smaller.
Then we want to also tell the price to never shrink, and to also centre the anonymous box (the content):
.currency {
/* make anonymous box vertically centred */
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
/* make sure it never shrinks */
-webkit-flex-shrink: 0;
flex-shrink: 0;
}
Lastly, we want to also center the label text. As you need the ellipsis hack, I've used the old Flexbox syntax for WebKit. Without using old flexbox, the line-clamp property does not work:
.label {
/* need to use old flexbox for line-clamp to work
it is a *horrible hack* */
display: -webkit-box;
display: flex;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow : hidden;
text-overflow: ellipsis;
-webkit-box-pack: center;
align-items: center;
}
As the other two items will never shrink, the .label element is the only one that can, so it reduces in size if there is no space left.

Related

Automatically span item width in CSS Grid

I am using CSS Grid to display some tags. If a tag is large (ie. it's width is more than 150px), I would like that item to span into more columns as needed. For example, in the image I would like the red tag to span into two columns so that the text remains in one line.
Is it possible to do something like that without adding a specific class to the target element? I am mapping through an array in React to generate each of these divs so it won't be possible to add a class to that element only.
index.js
<div className={styles.container}>
<>
{tags.map(tag => {
return <TagBlock tag={tag} />
})}
</>
</div>
style.css
.container {
margin: 30px auto;
width: 90%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, auto));
grid-gap: 20px;
}
Don't know a way to implement a gradual column width increase via CSS only. A JS logic will be required to set "how many columns it should take". Then one of the following cases.
explicitly style for the "wide" column:
grid-column: span X;
where X is how many columns should take your element.
Set predefined classes (like at Bootstrap: col-1, col-2), then apply them.
If a column could have any width, then I would recommend you to use a flexbox with wrapping. Kind of:
.container {
margin: 30px auto;
width: 90%;
/*added properties below*/
display: flex;
flex-wrap: wrap;
align-content: flex-start;
}
/*TagBlock base style that grid generated on its own*/
TagBlock {
margin: 25px;
min-height: 120px;
min-width: 120px;
}
Hope, this will help a little.

Angular Material Select w/ Columns

I am trying to style the selection panel such that the items display in several columns, but the last item of each column seems to become off-center and split, with the overflow starting at the top of the next column. Ideally the scroll would be vertical, but instead it seems to scroll horizontal to cover the overflow. My list has 30+ items and multiple selections are allowed. The goal is to display as many options as possible to the user at once so they don't have to scroll too much.
Full StackBlitz:
https://stackblitz.com/edit/angular-bt3gs6
select-multiple-example.scss
.toppings-panel.mat-select-panel {
column-count: 2;
column-width: 200px;
width: 400px;
max-width: 400px;
}
select-multiple-example.html
<mat-select [formControl]="toppings" panelClass="toppings-panel" multiple>
<mat-option *ngFor="let topping of toppingList" [value]="topping">{{topping}}</mat-option>
</mat-select>
select-multiple-example.ts
export class SelectMultipleExample {
toppings = new FormControl();
toppingList: string[] = ['Pepperoni', 'Sausage', 'Ham', 'Bacon', 'Chicken',
'Mushroom', 'Red onion', 'White onion', 'Tomato', 'Olives',
'Green bell peppers', 'Pineapple', 'Artichoke', 'Spinach',
'Basil', 'Hot pepper flakes',
'Parmesan', 'Shredded cheddar', 'Extra mozzarella'];
}
The problem with the columns that become off-center and split, is the height of the .mat-select-panel.
It has max-height:256px; (setted in Angular Material code). But, since it has a horizontal scrollbar (which has a height of 17px, in Windows - Chrome), the available remaining space will be: 256 - 17 = 239px.
The height of the mat-option is 48px, so 5 options in a column will take 240px.
A quick solution would be to increase the height of the .mat-select-panel to 257px:
Demo: https://stackblitz.com/edit/angular-bt3gs6-wxwkgg
But, the example above will not display correctly on MacOS; which displays scrollbars like an absolute positioned content, and it has more space available:
I've found a cross platform solution by removing columns (which are kind of difficult to implement cross-browser and cross-platform) and taking an
approach with display: flex for the .mat-select-panel element:
Horizontal scrolling:
.toppings-panel.mat-select-panel {
width: 400px;
max-width: 400px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
min-height: 257px; // min-height needed for windows browsers
}
.toppings-panel.mat-select-panel .mat-option {
min-width: 50%; // 50% to have 2 columns visible
}
Demo: https://stackblitz.com/edit/angular-bt3gs6-lwvwav
Vertical scrolling:
If you prefer vertical scrolling, just remove flex-direction: column; from the code above and play with the max-height in order to set the default visible rows:
.toppings-panel.mat-select-panel {
width: 400px;
max-width: 400px;
display: flex;
flex-wrap: wrap;
max-height: 240px; /* 240px - for 5 items / column */
}
.toppings-panel.mat-select-panel .mat-option {
min-width: 50%;
}
Demo: https://stackblitz.com/edit/angular-bt3gs6-iykn4w

Flex row occupying height even without the presence of content

I use vuejs and quasar framework, I'm customizing a third party component, there is a point where I have nested flex-rows, this is all defined by the inner classes of the quasar, so I can not control this directly so I'm overwriting some parts of the css .
My problem is that even when I have no content the flex-row is rendering a height, and this is not expected, since the row is empty. I need to know how to fix this.
Since some settings come from the quasar and I do not have access I will make available what the browser gives me data after rendering.
the tag with the problem in question is a div:
div{
align-content: normal;
align-items: normal;
align-self: auto;
alignment-baseline: auto;
direction: ltr;
display: flex;
flex-basis: auto;
flex-direction: row;
flex-grow: 0;
flex-shrink: 1;
flex-wrap: nowrap;
height: auto;
}
Apparently the height auto is generating this.

Force flex element not to grow in cross-axis direction

I'm making a page with a vertical menu using display: flex;. I want the menu's width to fit snuggly around a few buttons, without having to use a fixed width.
However, I also want the menu box to have a status message, which can have quite a long text. I'd like this status-div to have the width of the menu, while not forcing the menu container to grow its width. Instead, the status-div should grow its height and wrap the text.
Explaining this in words is pretty difficult, so I suggest you checkout out this fiddle: http://jsfiddle.net/bXL3q/
Note the difference when setting .statusmessage to display: none;.
Any ideas, or is what I'm trying to do not feasible? ..should it be?
What I've tried:
width: 100% fails, obviously it just assumes the parent width
width: -webkit-min-content sort of works, but it makes the element too narrow
flex-basis and flex-grow affect the height of the element, and do nothing to affect the width
position: absolute will solve the width issues, but now I have no way to define the height of the status-div.. (for the purpose of forcing a scroll bar in windows with small height - instead it will just flow over the button elements)
body {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
flex-flow: row nowrap;
align-items: stretch;
}
.page {
flex-grow: 1;
background-color: yellow;
}
.menu {
background-color: red;
height: 100%;
display: flex;
flex-flow: column nowrap;
}
.somechildren {
white-space: nowrap;
background-color: green;
border: 1px solid black;
}
.menu>* {
flex-shrink: 0;
}
.separate {
flex-grow: 1;
}
.statusmessage {
background-color: magenta;
align-self: flex-end;
/*display: none;*/
}
<div class=menu>
<div class=somechildren>I'd like the menu's</div>
<div class=somechildren>width to fit nicely</div>
<div class=somechildren>around these children</div>
<div class=separate></div>
<div class=statusmessage>
While forcing this status message to wrap and grow its height, without affecting the width of the container.
</div>
</div>
<div class=page>
The page
</div>
You were almost there with width. What you need to do is set width and min-width (demo):
.statusmessage {
width:0; /* Collapses .statusmessage so it doesn't affect column width */
min-width:100%; /* Expands .statusmessage to width of column */
}
The width can be (and probably should be) set to a value other than 0. It should just be the minimum width of the column or smaller. So use a value that works for you.
I've tested this on Chrome and Firefox and seems to work in both. Now, is it supposed to work? I'm not sure, I haven't read into the spec that much (it could be undefined). Make sure to test in all browsers you need it to work in. (And check the spec to see if this behavior is undefined/incorrect.)
width: 0; min-width: 100%; didn't work for me.
Instead, I set
position: relative;
on the flex child and wrapped its contents in an inner div with
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
This prevents the contents from contributing to the flex container size while still matching the cross-axis size determined by the rest of the flex children.
For me, the issue was that the default value of align-items is stretch. So the items stretch out the cross axis by default.
You can either set align-items: flex-start or align-self: flex-start for the single flex child.
The visual here is quite illustrative.

CSS3 column layout with dynamic number of columns

I have a set of images that I want to display in the following pattern:
[1] [4] [7] [10] [13]
[2] [5] [8] [11] ...
[3] [6] [9] [12]
I know that I can always manually group 3 images into a div.column or something similar, but I want to achieve this layout with as simple HTML as possible. The images are 225x150.
Currently, I have the following HTML:
<div class='album'>
<img src='img/01.jpg' />
<img src='img/01.jpg' />
...
</div>
And here's my stylesheet:
.album {
background: #faa;
display: block;
-webkit-column-count:2;
-webkit-column-gap: 10px;
height: 450px;
width: 460px;
}
.album img {
display: block;
width: 100%;
}
Chapter 8.2 in the specs describes that if I specify a height and the content won't fit in, more columns are created as needed, which is basically just what I want.
As you can see, I specified a background color for the .album. This does only cover the first two columns though, since I set the width to 460px. However, I really need an element that has the exact size/width of the album's content, i.e. an element wrapping the album with that exact size.
None of the possibilities I tried seemed to work tho. (100%, auto, played with overflow as well)
Does anyone have an idea on how I could create such a wrapper element for my albums?
You may be stuck using Flexbox for this purpose, since using columns within inline elements (inline-block, etc.) causes some browsers to miscalculate the width of the element.
http://codepen.io/cimmanon/pen/vuxjF
.album {
background: #faa;
height: 480px;
padding: 5px;
display: -ms-inline-flexbox;
display: -webkit-inline-flex;
-webkit-flex-flow: column wrap;
-ms-flex-flow: column wrap;
flex-flow: column wrap;
-ms-flex-align: start;
-webkit-align-items: flex-start;
align-items: flex-start;
}
#supports (flex-wrap: wrap) {
.album {
display: inline-flex;
}
}
.album img {
margin: 5px;
}
Similary thing was asked also at With column-count, can you dynamically change from 3 to 2 columns if resolution smaller?. CSS columns answered there might be good to check? (columns is a shorthad for column-width + column-count, see the spec & codepen demo).
Note that browser support varies & -moz/-webkit prefixes are needed currently (http://caniuse.com/#search=column).

Resources