Cross-browser horizontal scroll with flex box - javascript

I've being trying to get a consistent horizontal scroll with flex-box.
http://codepen.io/sheriffderek/pen/NABzdQ
Things were going great, but I did that thing ~ where you forget to look at it in Safari and FireFox. Things aren't working great there. Normally, if something works in web-kit but not firefox, webkit is smoothing out something that isn't right / where as firefox follows the rules. So, it's not a bug or anything... but the flexy things aren't really expanding around their child images.
That being said, I can't figure out which part is the pivotal thing that needs to change.
I'm using flex-box for the header and 'content' area where the content area grows to fill the remaining space. So, a global column
Within the 'content' area, there is a list that is flex and row. In the list there are items with either text or an image. The images should be 100% height of the list, and then auto width.
I would have thought that I could set the images to 100% height and width auto (similar to how I do the opposite for small-screens) - but the affect is not the same.
img {
display: block;
height: 100%;
width: auto;
}
to deal with this, I use JS to find the height of the 'content' area and set the images to that height.
http://s.codepen.io/sheriffderek/debug/NABzdQ (version with no editor for testing / on iPad etc.)
Works great in Chrome.
So... Does anyone have any advice? My real project is small-screen first, so more complex - but this is a clear example of where things go awry.
Styles
ul
list-style: none
margin: 0
padding: 0
body
display: flex
flex-direction: column
height: 100vh // ?
.header
//
.section // content
display: flex
flex-grow: 1
.item-list
display: flex
flex-direction: row
flex-grow: 1
max-width: 100%
overflow: auto
.item
display: flex
flex-grow: 1
a //
display: block
display: flex
.image-w
width: auto // should stretch to fit the child / image
max-height: 100%
overflow: hidden //?
img
display: block
width: auto
// height to be set by JS
.text-w
width: 400px
padding: 1rem
Scripts
var setUp = function() {
$('.item').each( function() {
var $this = $(this);
// var $thisFigure = $this.find('.image-w');
var $thisImage = $this.find('img');
var thisHeight = $this.outerHeight();
$thisImage.css({
height: thisHeight
});
});
};
$(window).resize( setUp ).trigger('resize');

Related

Create a collapsible divs

I'm trying to create a collapsible divs (arranged in a column) which takes always the same height when open.
For example, if one div is open, it takes 100% of the height. If two are open, each takes 50% etc.
I set flex-grow: 1 on each div, but the one with bigger content is always higher then others. I tried also to set
fles-basis: 0, but then they do not open at all.
Here is the link to my minimal example: https://codesandbox.io/s/nifty-meadow-hyr31
In your Collapsible.module.css add height=100vh to open style:
.open {
overflow: auto;
height: 100vh;
}
To explain why the solution above works, one vh is equal to 1% of the viewport, the browser will always compute the values in pixels, to see a simple example :
one = document.querySelector('.one');
two = document.querySelector('.two');
one.innerText = one.clientHeight;
two.innerText = two.clientHeight;
body {
height: 100vh;
display: flex;
flex-direction: column;
}
div {
height: 100vh;
}
.one {
background-color: chocolate;
}
.two {
background-color: cyan;
}
.three {
background-color: darkgray;
}
<div class="one">one</div>
<div class="two">two</div>
If you add a third div with a class 'three' the browser will assign each the third of the available height of the viewport. 100vh is not an absolute measure, the only absolute values are the one you put in pixels (although even that can change sometimes if your elements are not considered responsive enough by the browser).

How to implement this sticky-header scroll-down component?

I have a div container that is about 70% of the height of the page. The 30% which is outside the div is dim lighted (greyed out).
I am trying to implement a functionality where scrolling down within this container div causes the container div to fill up more of the page (less vertical space is greyed out) and eventually all of it (so 100% height).
Vica versa, when scrolling upward within the container and reaching the top should cause of the greyed out space to become bigger. What is the easiest way to implement this, possibly with the help of a library?
Using the scroll event and a combination of scrollTop, scrollHeight and clientHeight properties, we could get something that resembles your need:
let elem = document.querySelector('div');
elem.addEventListener('scroll', () => {
if(elem.scrollTop == 0) {
elem.style.height = `${elem.clientHeight + 10}px`;
}
if(elem.scrollHeight - elem.clientHeight == elem.scrollTop) {
elem.style.height = `${elem.clientHeight + 1}px`;
}
});
body {
background: #555;
height: 100vh;
margin: 0;
display: flex;
align-items: center;
overflow-y: hidden;
}
div {
background: #ccc;
height: 70%;
width: 100%;
overflow-y: scroll;
}
<body>
<div>
<br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br><br>
</div>
</body>

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.

Dynamically reset width of div using JavaScript?

I'm trying to animate a slide in menu using CSS/JavaScript (SASS/CoffeSript). It works like this, the only problem is that the width of the menu which slides in is hardcoded (233px). How can I dynamically get the width of the div and set it back, when I need to set the width of the div to 0 in CSS on start? Can I somehow calculate what the div width should be and set this again?
Any ideas how I might solve this to get the width dynamically, so that I can add/remove items to the menu without changing a hardcoded value in the CoffeScript?
HTML/PUG:
// Module: Header
.header
.header--logo
a(href="/", class="link-logo")
img(src="/img/logo.svg")
.header--menu#menu
a(href="#", class="link-menu")
img(src="/img/menu.svg", id="clicked-menu-item")
ul#nav
li: a(href="") projects
li: a(href="") contact
li: a(href="") self
CSS/SASS:
.header
position: sticky
top: 0
left: 0
background-color: $lightColor
display: flex
justify-content: space-between
padding: $baselineHeight
&--menu
display: flex
align-items: center
ul
width: 0px
overflow-x: hidden
transition: width ease-in-out 300ms
li
display: inline
margin-left: $baselineHeight
JavaScript/CoffeScript:
element = document.getElementById('clicked-menu-item')
element.onclick = (obj) ->
if (obj.srcElement.classList.contains('clicked'))
document.getElementById("nav").style.width = "0px"
else
document.getElementById("nav").style.width = "233px"
obj.srcElement.classList.toggle('clicked')
false

Flexbox container resize possible? [duplicate]

I am working on a nested flexbox layout which should work as follows:
The outermost level (ul#main) is a horizontal list that must expand to the right when more items are added to it. If it grows too big, there should be a horizontal scroll bar.
#main {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
overflow-x: auto;
/* ...and more... */
}
Each item of this list (ul#main > li) has a header (ul#main > li > h2) and an inner list (ul#main > li > ul.tasks). This inner list is vertical and should wrap into columns when needed. When wrapping into more columns, its width should increase to make room for more items. This width increase should apply also to the containing item of the outer list.
.tasks {
flex-direction: column;
flex-wrap: wrap;
/* ...and more... */
}
My problem is that the inner lists don't wrap when the height of the window gets too small. I have tried lots of tampering with all the flex properties, trying to follow the guidelines at CSS-Tricks meticulously, but no luck.
This JSFiddle shows what I have so far.
Expected result (what I want):
Actual result (what I get):
Older result (what I got in 2015):
UPDATE
After some investigation, this is beginning to look like a bigger issue. All major browsers behave the same way, and it has nothing to do with my flexbox design being nested. Even simpler flexbox column layouts refuse to increase the list's width when the items wrap.
This other JSFiddle clearly demonstrates the problem. In current versions of Chrome, Firefox and IE11, all items wrap correctly; the list's height increases in row mode, but its width does not increase in column mode. Also, there is no immediate reflow of elements at all when changing the height of a column mode, but there is in row mode.
However, the official specs (look specifically at example 5) seem to indicate that what I want to do should be possible.
Can someone come up with a workaround to this problem?
UPDATE 2
After a lot of experimenting using JavaScript to update the height and width of various elements during resize events, I have come to the conclusion that it is too complex and too much trouble to try to solve it that way. Also, adding JavaScript definitely breaks the flexbox model, which should be kept as clean as possible.
For now, I'm falling back to overflow-y: auto instead of flex-wrap: wrap so that the inner container scrolls vertically when needed. It is not pretty, but it is one way forward that at least does not break useability too much.
The Problem
This looks like a fundamental deficiency in flex layout.
A flex container in column-direction will not expand to accommodate additional columns. (This is not a problem in flex-direction: row.)
This question has been asked many times (see list below), with no clean answers in CSS.
It's hard to pin this as a bug because the problem occurs across all major browsers. But it does raise the question:
How is it possible that all major browsers got the flex container to
expand on wrap in row-direction but not in column-direction?
You would think at least one of them would get it right. I can only speculate on the reason. Maybe it was a technically difficult implementation and was shelved for this iteration.
UPDATE: The issue appears to be resolved in Edge v16.
Illustration of the Problem
The OP created a useful demo illustrating the problem. I'm copying it here: http://jsfiddle.net/nwccdwLw/1/
Workaround Options
Hacky solutions from the Stack Overflow community:
"It seems this issue cannot be solved only with CSS, so I propose you a JQuery solution."
"It's curious that most browsers haven't implemented column flex containers correctly, but the support for writing modes is reasonably good. Therefore, you can use a row flex container with a vertical writing mode."
More Analysis
Chromium Bug Report
Mark Amery's answer
Other Posts Describing the Same Problem
Flex box container width doesn't grow
How can I make a display:flex container expand horizontally with its wrapped contents?
Flex-flow: column wrap. How to set container's width equal to content?
Flexbox flex-flow column wrap bugs in chrome?
How do I use "flex-flow: column wrap"?
Flex container doesn't expand when contents wrap in a column
flex-flow: column wrap, in a flex box causing overflow of parent container
Html flexbox container does not expand over wrapped children
Flexbox container and overflowing flex children?
How can I make a flexbox container that stretches to fit wrapped items?
Flex container calculating one column, when there are multiple columns
Make container full width with flex
Flexbox container resize possible?
Flex-Wrap Inside Flex-Grow
Flexbox grow to contain
Expand flexbox element to its contents?
flexbox column stretch to fit content
https://stackoverflow.com/q/48406237/3597276
flex-flow: column wrap doesn't stretch the parent element's width
Why doesn't my <ul> expand width to cover all the <li>?
https://stackoverflow.com/q/55709208/3597276
Flexbox wrap not increasing the width of parent?
Absolute Flex container not changing to the correct width with defined max-height
Late to the party, but was still running into this issue YEARS later. Ended up finding a solution using grid. On the container you can use
display: grid;
grid-auto-flow: column;
grid-template-rows: repeat(6, auto);
I have an example on CodePen that toggles between the flexbox issue and the grid fix: https://codepen.io/MandeeD/pen/JVLdLd
CSS-only workaround
Nearly 6 years after this question was asked, this flexbox bug still exists, so here's a CSS-only flex-direction: column workaround for anyone else that ends up here:
body {
background-color: grey;
}
button {
background-color: white;
border: none;
border-radius: 4px;
width: 80px;
height: 40px;
margin: 4px;
}
/* WORKAROUND FOR flex-direction: column WITH WRAP IS BELOW */
.wrapped-columns {
flex-direction: row;
flex-wrap: wrap;
writing-mode: vertical-lr;
text-orientation: upright;
}
/* Ensures content is rendered correctly in Firefox */
.wrapped-columns * {
writing-mode: horizontal-tb;
}
<div class="wrapped-columns">
<button>Button 1</button>
<button>Button 2</button>
<button>Button 3</button>
<button>Button 4</button>
<button>Button 5</button>
<button>Button 6</button>
<button>Button 7</button>
<button>Button 8</button>
<button>Button 9</button>
<button>Button 10</button>
<button>Button 11</button>
<button>Button 12</button>
<button>Button 13</button>
<button>Button 14</button>
</div>
This workaround gives the same outcome as flex-direction: column and works with both flex-wrap: wrap and wrap-reverse.
I just found a really awesome PURE CSS workaround here.
https://jsfiddle.net/gcob492x/3/
The tricky: set writing-mode: vertical-lr in the list div then writing-mode: horizontal-tb in the list item. I had to tweak the styles in the JSFiddle (remove a lot of the alignment styles, which aren't necessary for the solution).
Note: the comment says it only works in Chromium-based browsers, and not Firefox. I've only personally tested in Chrome. It's possible either there's a way to modify this to make it work in other browsers or there have been updates to said browsers that make this work.
Big shoutout to this comment: When flexbox items wrap in column mode, container does not grow its width. Digging through that issue thread led me to https://bugs.chromium.org/p/chromium/issues/detail?id=507397#c39 which led me to this JSFiddle.
It is unfortunate that so many major browsers suffer from this bug after many years. Consider a Javascript workaround. Whenever the browser window resizes, or content is added to the element, execute this code to get it to resize to the proper width. You can define a directive in your framework to do it for you.
element.style.flexBasis = "auto";
element.style.flexBasis = `${element.scrollWidth}px`;
Since no solution or proper workaround was suggested yet, I managed to obtain the requested behavior with a little different approach. Instead of separating the layout into 3 different divs, I'm adding all the items into 1 div and creating the separation with some more divs in between.
The proposed solution is hard coded, assuming we have 3 sections, but can be extended to a generic one. The main idea is to explain how we can achieve this layout.
Adding all the items into 1 container div that uses flex to wrap the items
The first item of each "inner container" (I'll call it a section) will have a class, which helps us to do some manipulations that create the separation and styling of each section.
Using :before on each first item, we can locate the title of each section.
Using space creates the gap between the sections
Since the space won't cover the full height of the section I'm also adding :after to the sections so positioning it with absolute position and white background.
To style the background color of each section I'm adding another div inside the first item of each section. I will be position with absolute as well and will have z-index: -1.
To get the correct width of each background, I'm using JS, setting the correct width, and also adding a listener to resize.
function calcWidth() {
var size = $(document).width();
var end = $(".end").offset().left;
var todoWidth = $(".doing-first").offset().left;
$(".bg-todo").css("width", todoWidth);
var doingWidth = $(".done-first").offset().left - todoWidth;
$(".bg-doing").css("width", doingWidth);
var doneWidth = $(".end").offset().left - $(".done-first").offset().left;
$(".bg-done").css("width", doneWidth + 20);
}
calcWidth();
$(window).resize(function() {
calcWidth();
});
.container {
display: flex;
flex-wrap: wrap;
flex-direction: column;
height: 120px;
align-content: flex-start;
padding-top: 30px;
overflow-x: auto;
overflow-y: hidden;
}
.item {
width: 200px;
background-color: #e5e5e5;
border-radius: 5px;
height: 20px;
margin: 5px;
position: relative;
box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.75);
padding: 5px;
}
.space {
height: 150px;
width: 10px;
background-color: #fff;
margin: 10px;
}
.todo-first:before {
position: absolute;
top: -30px;
height: 30px;
content: "To Do (2)";
font-weight: bold;
}
.doing-first:before {
position: absolute;
top: -30px;
height: 30px;
content: "Doing (5)";
font-weight: bold;
}
.doing-first:after,
.done-first:after {
position: absolute;
top: -35px;
left: -25px;
width: 10px;
height: 180px;
z-index: 10;
background-color: #fff;
content: "";
}
.done-first:before {
position: absolute;
top: -30px;
height: 30px;
content: "Done (3)";
font-weight: bold;
}
.bg-todo {
position: absolute;
background-color: #FFEFD3;
width: 100vw;
height: 150px;
top: -30px;
left: -10px;
z-index: -1;
}
.bg-doing {
position: absolute;
background-color: #EFDCFF;
width: 100vw;
height: 150px;
top: -30px;
left: -15px;
z-index: -1;
}
.bg-done {
position: absolute;
background-color: #DCFFEE;
width: 10vw;
height: 150px;
top: -30px;
left: -15px;
z-index: -1;
}
.end {
height: 150px;
width: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<div class="item todo-first">
<div class="bg-todo"></div>
Drink coffee
</div>
<div class="item">Go to work</div>
<div class="space"></div>
<div class="item doing-first">
<div class="bg-doing"></div>
1
</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="space"></div>
<div class="item done-first">
<div class="bg-done"></div>
1
</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="end"></div>
</div>
I solved my issue in this manner I hope it helps someone else that stumbles on thid :
_ensureWidth(parentElement) {
let totalRealWidth = 0;
let parentElementBoundingRect = parentElement.getBoundingClientRect()
let lowestLeft = parentElementBoundingRect.x;
let highestRight = parentElementBoundingRect.width;
for (let i = 0; i < parentElement.children.length; i++) {
let { x, width } = parentElement.children[i].getBoundingClientRect();
if (x < lowestLeft) {
lowestLeft = x;
}
if (x + width > highestRight) {
highestRight = x + width;
}
}
totalRealWidth = highestRight - lowestLeft;
parentElement.style.width = `${totalRealWidth}px`;
}
Workaround :
using javascript its not hard to set the wrapper's width manually after elements have loaded on the screen. The width would always be the last child element's right hand point.
In react i have it updated on the layout changes based on any children being added to the flex wrapper , but this could be called at any point you add or remove children to the wrapper .
let r = refWrapper.current.lastElementChild.getBoundingClientRect()
refWrapper.current.style.width = (r.x+r.width )+'px'
`
where refWrapper is your your flex element
Possible JS solution..
var ul = $("ul.ul-to-fix");
if(ul.find("li").length>{max_possible_rows)){
if(!ul.hasClass("width-calculated")){
ul.width(ul.find("li").eq(0).width()*ul.css("columns"));
ul.addClass("width-calculated");
}
}

Categories