I'm trying to come up with an effective way of defining a nested flexbox and allow it to be resized. I think it's almost there:
http://jsfiddle.net/6j10L3x2/1/
I'm using three custom elements purely to make the mark-up more declarative:
flex, flex-item, flex-resizer
A flex represents the container. A flex-item presents an element within the container, and flex-resizer represents a resizer widget which can be placed between two flex-items to add resizing functionality between them.
This all appears to work really well. However, it only handles items sized with flex-grow. If flex-shrink or flex-basis is defined, then the calculations simply don't work.
Can anyone suggest a way to amend this to allow it work for all cases? I realise that there is some ambiguity in regards to how the space should be shared between items with various flex configurations, but any input would be welcome.
Any alternative approaches would be welcome also. Thanks.
Wow. I am impressed how you resize the flexbox elements with vanilla javascript using 'flexGrow', excelent idea and code.
I have improve your code in a few ways and it is working very well.
What I did?
1.- I simplified the HTML:
Do not use a flex element inside a flex-item.
Use a flex or flex-item element, always!, inside another
flex element.
2.- Solved!
Splitter's jump when the visible flex-item size is smaller that its content size.
3.- I'd added different cursors to signal a state's change (setupResizerEvents, onMouseUp) to improve usability.
4.- I've added code to prevent the cursor from flickering when dragging.
5.- use of offsetWidth and offsetHeight in manageResize() versus scrollWidth and scrollHeight to avoid splitter's jump on resize when a flex-item content overflow (overflow: auto).
Here is the code:
function manageResize(md, sizeProp, posProp) {
var r = md.target;
var prev = r.previousElementSibling;
var next = r.nextElementSibling;
if (!prev || !next) {
return;
}
md.preventDefault();
var prevSize = prev[sizeProp];
var nextSize = next[sizeProp];
var sumSize = prevSize + nextSize;
var prevGrow = Number(prev.style.flexGrow);
var nextGrow = Number(next.style.flexGrow);
var sumGrow = prevGrow + nextGrow;
var lastPos = md[posProp];
function onMouseMove(mm) {
var pos = mm[posProp];
var d = pos - lastPos;
prevSize += d;
nextSize -= d;
if (prevSize < 0) {
nextSize += prevSize;
pos -= prevSize;
prevSize = 0;
}
if (nextSize < 0) {
prevSize += nextSize;
pos += nextSize;
nextSize = 0;
}
var prevGrowNew = sumGrow * (prevSize / sumSize);
var nextGrowNew = sumGrow * (nextSize / sumSize);
prev.style.flexGrow = prevGrowNew;
next.style.flexGrow = nextGrowNew;
lastPos = pos;
}
function onMouseUp(mu) {
// Change cursor to signal a state's change: stop resizing.
const html = document.querySelector('html');
html.style.cursor = 'default';
if (posProp === 'pageX') {
r.style.cursor = 'ew-resize';
} else {
r.style.cursor = 'ns-resize';
}
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
}
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
}
function setupResizerEvents() {
document.body.addEventListener("mousedown", function (md) {
// Used to avoid cursor's flickering
const html = document.querySelector('html');
var target = md.target;
if (target.nodeType !== 1 || target.tagName !== "FLEX-RESIZER") {
return;
}
var parent = target.parentNode;
var h = parent.classList.contains("h");
var v = parent.classList.contains("v");
if (h && v) {
return;
} else if (h) {
// Change cursor to signal a state's change: begin resizing on H.
target.style.cursor = 'col-resize';
html.style.cursor = 'col-resize'; // avoid cursor's flickering
// use offsetWidth versus scrollWidth (and clientWidth) to avoid splitter's jump on resize when a flex-item content overflow (overflow: auto).
manageResize(md, "offsetWidth", "pageX");
} else if (v) {
// Change cursor to signal a state's change: begin resizing on V.
target.style.cursor = 'row-resize';
html.style.cursor = 'row-resize'; // avoid cursor's flickering
manageResize(md, "offsetHeight", "pageY");
}
});
}
setupResizerEvents();
body {
/* margin:0; */
border: 10px solid #aaa;
}
flex {
display: flex;
overflow: hidden;
}
/* flex-item > flex {
position: absolute;
width: 100%;
height: 100%;
} */
flex.h {
flex-direction: row;
}
flex.v {
flex-direction: column;
}
flex-item {
/* display: flex; */
/* position: relative; */
/* overflow: hidden; */
overflow: auto;
}
flex > flex-resizer {
flex: 0 0 10px;
/* background: white; */
background-color: #aaa;
background-repeat: no-repeat;
background-position: center;
}
flex.h > flex-resizer {
cursor: ew-resize;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='30'><path d='M2 0 v30 M5 0 v30 M8 0 v30' fill='none' stroke='black'/></svg>");
}
flex.v > flex-resizer {
cursor: ns-resize;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='30' height='10'><path d='M0 2 h30 M0 5 h30 M0 8 h30' fill='none' stroke='black'/></svg>");
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>flex-splitter</title>
<link rel="stylesheet" href="./src/styles.css">
<script src="./src/index.js" defer></script>
</head>
<body>
<flex class="v" style="flex: 1; height: 500px;">
<flex-item style="flex: 1;">Flex 1</flex-item>
<flex-resizer></flex-resizer>
<flex class="h" style="flex: 1;">
<flex-item style="flex: 1; background-color: aqua;">
<!--
The next section is an example to test the splitter when there is content inside a flex-item
-->
<section>
<div>
<label for="CursorCoor" style="display: block;">showCursorCoor: </label>
<textarea id="CursorCoor" rows="6" cols="50" wrap="soft" readonly></textarea>
</div>
<br />
<div>
<label for="boxInfo" style="display: block;">showBoxInfo: </label>
<textarea id="boxInfo" rows="6" cols="50" wrap="soft" readonly></textarea>
</div>
</section>
</flex-item>
<flex-resizer></flex-resizer>
<flex class="v" style="flex: 2; ">
<flex-item style="flex: 1; background: pink;">Flex 3</flex-item>
<flex-resizer></flex-resizer>
<flex class="h" style="flex: 1">
<flex-item style="flex: 1; background: green;">Flex 4</flex-item>
<flex-resizer></flex-resizer>
<flex-item style="flex: 2;">Flex 5</flex-item>
<!-- <flex-resizer></flex-resizer> -->
<flex-item style="flex: 3; background: darkorange;">Flex 6</flex-item>
</flex>
</flex>
</flex>
</flex>
</body>
</html>
Or see it on Codesandbox:
I hope it helps!
Note: There is also the new, basic resize CSS property, but it's only for bottom right corner dragging.
I did some research on this, and the first 3 framework-free, fully baked results I came across were, in order of appearances (untested):
https://daybrush.com/moveable
"Moveable is Draggable, Resizable, Scalable, Rotatable, Warpable, Pinchable, Groupable, Snappable"
I love the look of things here, both visually and code-wise! Seems highly functional and extremely flexible as well.
See also: https://github.com/daybrush/moveable https://daybrush.com/moveable/release/latest/doc
UPDATE: I tried this one out, I actually would not recommend it. It's very complicated to use, poorly documented, and I'd rather write my own JS instead.
https://split.js.org
Looks nice, but it appears to only be for split panels specifically, no e.g. corner dragging. If that's all you want, this might be a good option.
See also: https://github.com/nathancahill/split/tree/master/packages/splitjs https://github.com/nathancahill/split/tree/master/packages/split-grid
https://jspanel.de
Not as aesthetic as I would like, but seems like it has a good set of functionality and options.
See also: https://github.com/Flyer53/jsPanel4
I also found this: http://w2ui.com/web/home https://github.com/vitmalina/w2ui
Related
i am trying to put several images (five) next to each other in one row. The row should have the width of a 100%. It is important that the images all have the same height e.g.! Is there a way to manage this? I tried several code, e.g. a masonry, but it does not help me with the height of the images.
Thank you
(I am using Bootstrap if that's any help.)
Edited, new answer.
The code below should do the job.
Note that I have inserted comments between all the img tags in the html, this is to make sure that there is no spacing between the images and is therefore important for the code to work!
Also note that if you change the class of the div which contains the images, you will have to change the query selector in the javascript to match this.
Final note: the script is very laggy here in the code snippet. I tried it as an actual webpage and it was not laggy at all, so maybe try that too!
var repeat = true;
window.addEventListener("load", resizeImages);
window.addEventListener("resize", resizeImages);
function resizeImages() {
var i;
var images = document.querySelectorAll("div.row img");
var heights = [];
var widths = [];
for (i = 0; i < images.length; i++) {
heights.push(images[i].offsetHeight);
widths.push(images[i].offsetWidth);
}
var numerator = document.body.clientWidth;
for (i = 0; i < heights.length; i++) {
numerator *= heights[i];
}
var denominator = 0;
for (i = 0; i < widths.length; i++) {
var thisItem = widths[i];
for (i2 = 0; i2 < heights.length; i2++) {
if (i != i2) {
thisItem *= heights[i2];
}
}
denominator += thisItem;
}
var height = numerator / denominator;
for (i = 0; i < images.length; i++) {
images[i].height = height;
}
if (repeat) {
repeat = false;
setTimeout(function() {
resizeImages();
}, 300)
}
}
div.row {
white-space: nowrap;
}
<html>
<head>
<title></title>
</head>
<body>
<div class="row">
<img src="https://media.wired.com/photos/5926db217034dc5f91becd6b/master/w_1904,c_limit/so-logo-s.jpg" alt=""><!--
--><img style="padding-left:5px;" src="https://jessehouwing.net/content/images/size/w600/2018/07/stackoverflow-1.png" alt=""><!--
--><img src="http://www.andysowards.com/blog/assets/8-Best-Websites-That-Will-Hone-Your-Programming-Skills-7-1024x538.png" alt="">
</div>
</body>
</html>
I used CSS Grid for your implementation. .row is 100% width and each image can have its own custom scaling if need be.
Be aware, images are hard make constant because each image will come with its own ratio and size.
You can use background-size to set image property to your liking.
The background-size CSS property sets the size of the element's background image. The image can be left to its natural size, stretched, or constrained to fit the available space.
Hope the example below helps.
Reference background-size: https://developer.mozilla.org/en-US/docs/Web/CSS/background-size
Reference CSS Grid: https://css-tricks.com/snippets/css/complete-guide-grid/
img {
height: 130px;
width: 100%;
}
.row{
width: 100%;
display: grid;
grid-template-rows: auto;
grid-template-columns: repeat(5, 1fr);
/* If each individual picture column need modification*/
/* grid-template-columns: auto 1fr 200px 15px 10%; */
}
<div class="row">
<img src="https://loremflickr.com/cache/resized/8447_7961096220_79eb4fb07c_c_640_360_nofilter.jpg" alt="">
<img src="https://loremflickr.com/cache/resized/65535_40670884373_757596f5d1_b_640_360_nofilter.jpg" alt="">
<img src="https://loremflickr.com/cache/resized/65535_47957578362_73f1562d77_z_640_360_nofilter.jpg" alt="">
<img src="https://loremflickr.com/cache/resized/65535_46970967485_3456a6be5f_z_640_360_nofilter.jpg" alt="">
<img src="https://loremflickr.com/cache/resized/2544_3722368834_5584ab3bc1_z_640_360_nofilter.jpg" alt="">
</div>
I am creating a web page which needs has to display some movie covers all in a single page, without scrolling it, becouse it will be on display. the problem is that i want to get the content to resize instead of making the web page scrollable. I also need to support n movies (they are dependent). I've tried using flexbox twice, but it doesnt work. Also, I am using tailwindcss framework, but i don't think that's a problem as it is just css in form of classes...
<html class='h-full m-0 p-0'>
<head>
<link href="https://unpkg.com/tailwindcss#next/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class='h-full m-0 p-0'>
<div class='mx-10 mt-10 flex content-center items-center'>
<div class='flex flex-wrap'>
<!-- iterate over every movie -->
<div class='m-2 relative flex-grow h-full' style='flex-basis: 20%'>
<span class='px-2 py-1 rounded-full bg-blue-500 text-white absolute z-0' style='top: -0.5rem; right: -0.5rem'>0</span>
<img src='https://images.unsplash.com/photo-1525604803468-3064e402d70c' class: 'w-full' />
<span class='w-full opacity-75 bg-black text-white py-1 absolute z-0 inset-x-0 bottom-0 text-center px-2'>title</span>
</div>
<!-- end -->
</div>
</div>
</body>
</html>
EDIT: I added a the full example (with an example image take from unsplash) of what i want it to look like.
I don't think there is a way to do what you are asking without JavaScript. In the following example, I used CSS to maintain the ratio of the covers, and used JavaScript to calculate the maximum width that would keep the content from scrolling vertically.
Here is a fiddle.
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
let numCovers = 10;
let coverList = document.querySelector(".cover-list");
// this function determines what percentage of the cover-list's width
// each cover should take up. It will maximize how large they can be
// without making the total grid size larger than cover-list.
// This should prevent scrolling.
function getBasis() {
let ratio = 1.35; // height/width
let width = coverList.clientWidth;
let height = coverList.clientHeight;
// this loop is really slow, you may want to find a faster way
let col = 0;
let accWidth, accHeight, numRows;
do {
col++;
if (col > numCovers) {
// maximize height
return (height / ratio) + "px";
}
accWidth = width / col;
accHeight = accWidth * ratio;
numRows = Math.ceil(numCovers / col);
} while (accHeight * numRows > height);
return (100 / col) + "%";
}
function generateCovers() {
// clear existing covers
coverList.innerHTML = "";
let basis = getBasis();
for (let i = 0; i < numCovers; i++) {
let cover = document.createElement("div");
cover.classList.add("cover");
cover.style.flexBasis = basis;
let inner = document.createElement("div");
inner.classList.add("inner");
inner.style.backgroundColor = getRandomColor();
cover.append(inner);
coverList.append(cover);
}
}
let numCoversInput = document.querySelector("#num-covers");
numCoversInput.addEventListener("change", function() {
numCovers = Math.min(Math.max(this.value, 1), 500);
this.value = numCovers;
generateCovers();
});
generateCovers();
window.addEventListener("resize", function() {
let basis = getBasis();
coverList.querySelectorAll(".cover").forEach(function(el) {
el.style.flexBasis = basis;
});
});
body {
/* set margin to computable value for cover-list calcs */
margin: 5px;
}
#controls {
height: 25px;
}
.cover-list {
/* account for margin and controls with calc */
width: calc(100vw - 10px);
height: calc(100vh - 35px);
/* use flex so the content will wrap as desired */
display: flex;
flex-wrap: wrap;
align-content: start;
}
.inner {
/* padding percentages are based on width, so setting
the height to 0 and padding-bottom to a percentage
allows us to maintain a ratio */
width: 100%;
height: 0;
padding-bottom: 135%;
}
<div id="controls">
<label>
Number of covers: <input id="num-covers" type="number" value="10" min="1" max="500"/>
</label>
</div>
<div class="cover-list">
</div>
I have boxes that contain <ul> list content. These boxes have a slightly variable width but have a fixed height. This list content is dynamically generated containing anywhere from 0 - 200 list elements. However, because of the box height, I usually can display only 3-5 at a time. This is OK.
However, I have been artificially restricting those lists by using ASP.NET MVC code to only display the first 4 elements. This works in about 90% of cases - some boxes will still have overflow if all of the list items have a lot of text (see below).
I was wondering if there was a way in CSS to use like the overflow property or something and hide the list elements that don't fit? I have tried overflow: hidden on the <ul> to no avail. I imagine this is because the list doesn't know the height of the box that is is in or something
Clarification: Ideally, you wouldn't see any part of the <li> that doesn't fit. See this fiddle
I don't know how long any of these items will be ahead of time or how many elements their might even be, and the width/height of the box are not modifiable. Any ideas?
Original Fiddle: http://jsfiddle.net/emowbngx/1/
Example of Box HTML:
<div class="box">
<ul>
<li>List item</li>
<li>these LI's are dynamically generated</li>
<li>I have no idea of their length ahead of time</li>
<li>Seth Rollins</li>
</ul>
</div>
Box CSS:
.box {
height: 110px;
width: 350px;
min-width: 350px;
float: left;
margin: 8px 16px 8px 0;
position: relative;
border: 1px solid black;
}
I may have found a way to do this using a pure CSS solution. It involves using flexbox and a bit of cheating to show the bullet using a :before pseudo element.
It even allows centering visible li elements vertically inside the container.
fiddle: https://jsfiddle.net/aq34sb17/2/
CSS
ul {
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 100px;
max-width: 180px;
overflow: hidden;
padding-left: 15px;
justify-content: space-around;
}
li {
display: block;
width: 100%;
padding-left: 10px;
position: relative;
}
li:before {
content: '\2022';
position: absolute;
left: -5px;
}
and here's the solution applied to your fiddle: http://jsfiddle.net/emowbngx/7/
Here's my solution :
http://jsfiddle.net/gc5un34j/3/
Using element.offsetHeight to get the height of each element of your list and then comparing it to the container;
JQuery:
var listItems = $('ul li'); //The list rows
var listContainer = $('ul'); //The list
var listHeight = 0; //Contains the height of the 4 elements together
for(var i = 0; i < listItems.length; i++) {
listHeight += listItems[i].offsetHeight;
//If the row is bigger than the container, hides it, [0] to access the DOM Element
if(listContainer[0].offsetHeight < listHeight) {
$(listItems[i]).hide();
}
}
Pure JS in browsers supporting QuerySelectors:
var listItems = document.querySelectorAll('ul li'); //The list rows
var listContainer = document.querySelector('ul'); //The list
var listHeight = 0; //Contains the height of the 4 elements together
for(var i = 0; i < listItems.length; i++) {
listHeight += listItems[i].offsetHeight;
//If the row is bigger than the container, hides it
if(listContainer.offsetHeight < listHeight) {
listItems[i].style.display = 'none';
}
}
Tested in Chrome 44 and Firefox
This will not remove (always!) last child, but, maybe it could be solution (even nicer, imho):
http://jsfiddle.net/emowbngx/6/
$('.box').each(function(i) {
if ($(this).children().height() > $(this).height())
{
//$(this).find('li:last-child').css('display','none');
$(this).find('li').each(function(j) {
if ($(this).position().top + $(this).height() > $('.box').height()) {
$(this).css('display', 'none');
}
});
}
});
Note: didn't test it too long, try to change text in li's, and see by your self...
Please have a look at this fiddle: http://fiddle.jshell.net/ikmac/q7gkx
Use this link to test in the browser: http://fiddle.jshell.net/ikmac/q7gkx/show/
HTML:
<div class="nav">
test1
test2
test3
</div>
<div id="test1" class="test">test1</div>
<div id="test2" class="test">test2</div>
<div id="test3" class="test">test3</div>
CSS:
.nav {
position: fixed;
top: 20px;
left: 0;
width: 100%;
height: 20px;
background: #000;
}
.nav a {
float: left;
font-size: 20px;
color: #fff;
}
#test1 {
margin-top: 1000px;
height: 1000px;
background: red;
}
#test2 {
height: 1000px;
background: blue;
}
#test3 {
height: 1000px;
background: green;
}
This is what happens in Safari on iOS 5.0 (4.3 doesn't support position fixed):
The first time I click on one of the anchors the page jumps to the correct anchor. After that I cannot click one of the other links anymore. When I scroll up or down a bit the links become clickable again.
All other desktop browsers behave fine.
Does anyone ever had this issue before or knows how to fix it?
I have that problem aswell. And I kind of half solved it by letting javascript do the scrolling of the nav when a nav anchor is clicked. And because normal touch-scrolling does not give an event until the finger lets go of the screen, I use position:fixed which makes the touch-scrolling nicer than javascript can, see apples dev-site.
It is not the ultimate solution, but in my opinion it is better than not working at all. This script also checks the width of the window to make sure that it only applies this to smaller screens, well, devices.
Here is my code, and if you find it useful, make it better or find a better solution, please share :)
/* NAV POSITION */
var specScroll = false; // If special scrolling is needed
/* Check what kind of position to use.*/
(function navPos() {
var width = checkWidth();
if (width <= 480 || navigator.userAgent.match(/iPad/i) != null) {
specScroll = true;
}else{
specScroll = false;
window.onscroll = NaN;
}
})();
$(window).resize( function(){ navPos(); } ); // After resizing, check what to use again.
/* When clicking one of the nav anchors */
$(function() {
$('a').bind('click',function(e){
var $anchor = $(this);
if(specScroll){
$('#nav').css('position', "absolute");
window.onscroll = anchorScroll;
}
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top
}, 700,'easeOutExpo', function(){
if(specScroll){setTimeout("window.onscroll = touchScroll;", 100);}
// the set timeout is needed for not overriding the clickability of the anchors after anchor-scrolling.
});
e.preventDefault();
});
});
/* While the user clicks and anchors in nav */
function anchorScroll() { $('#nav').css('top', window.pageYOffset); }
/* the first time the user scrolls by touch and lift the finger from screen */
function touchScroll() {
$('#nav').css('position', 'fixed');
$('#nav').css('top', 0);
window.onscroll = NaN;
}
/* CHECK WIDTH OF WINDOW */
function checkWidth() {
myWidth = 0;
if( typeof( window.innerWidth ) == 'number' ) {
myWidth = window.innerWidth; //Non-IE
} else if( document.documentElement && ( document.documentElement.clientWidth ) ) {
myWidth = document.documentElement.clientWidth; //IE 6+ in 'standards compliant mode'
} else if( document.body && ( document.body.clientWidth ) ) {
myWidth = document.body.clientWidth; //IE 4 compatible
}
return myWidth;
}
I use this solution on a project page, try it out: dare.niklasek.se
I ran into the same issue using a fixed position navigation that scrolls the user around the page using jQuery animation. What I found is that even though the fixed position element is visible at the new position, inspecting it with js reports that it is still back in the original position until the user moves the screen manually. Until then, even though the nav is there visually, it can't be touched in order to interact with it. More information and demo here: http://bit.ly/ios5fixedBug
It looks like I'm missing something basic.
I want a certain DIV to have the height of the window. The effect should be that there's never any need to scroll down.
Do I have to use JavaScript? I was told that it's possible with css, but I can't find the relevant property - or I'm doing something wrong.
Here is the trick:
body {
margin:0;
padding:0;
height:100%; /* this is the key! */
}
.yourDivStyle {
...
height: 100%;
}
If you want to turn scrolling off, use this CSS:
html, body
{
overflow: hidden;
}
But remember now the content will not scroll - you can put overflow:auto or overflow:scroll on individual divs if you need some scrolling.
If you use Javascript make sure to register for the onresize event so that you can change numbers if the window is resized.
Make all parents have height: 100%; or use position: absolute /* or fixed */; left: 0; top: 0; width: 100%; height: 100%;.
Do I have to use JavaScript? I was
told that it's possible with css, but
I can't find the relevant property -
or I'm doing something wrong.
Javascript is the way to go, you can not determine winodow size with css and and re-calculate based on window resizing.
Ok, so in Javascript (not jQuery or anything):
<html>
<head>
<script type="text/javascript">
<!--
var sWidth;
var sHeight;
if (typeof window.innerWidth != 'undefined')
{
sWidth = window.innerWidth,
sHeight = window.innerHeight
} else if (typeof document.documentElement != 'undefined'
&& typeof document.documentElement.clientWidth !=
'undefined' && document.documentElement.clientWidth != 0)
{
sWidth = document.documentElement.clientWidth,
sHeight = document.documentElement.clientHeight
} else
{
sWidth = document.getElementsByTagName('body')[0].clientWidth,
sHeight = document.getElementsByTagName('body')[0].clientHeight
}
//VARIABLES STORED IN sWidth AND sHeight
document.write('<div style="height: '+sHeight+'; width: '+sWidth+';">CONTENT</div>');
//-->
</script>
</head>
<body>
</body>
</html>
Obviously you could write the div out seperately ^_^