I have dynamic content and a responsive layout, so the number of items and available width will vary. Sometimes elements in a div will need to wrap onto a second 'row'.
With flexbox (or any other CSS method) can you make the number of items on each row be equal?
<div class="cont"> <div class="elem"></div> <div class="elem"></div> <div class="elem"></div> <div class="elem"></div> <div class="elem"></div> <div class="elem"></div> <div class="elem"></div> </div> .cont { display: -webkit-flex; display: -ms-flexbox; display: flex; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap; border: 1px solid grey; margin: auto; width: 60%; padding: 10px; } .elem { height: 100px; width: 100px; border: 1px solid blue; display: -webkit-flex; display: -ms-flexbox; display: flex; margin-right: 10px; margin-bottom: 10px; }
http://codepen.io/anon/pen/qEQzqY
Add a width to the . child elements. I personally would use percentages on the margin-left if you want to have it always 4 per row.
For 3 items per row, add on the flex items: flex-basis: 33.333333% You can also use the flex 's shorthand like the following: flex: 0 0 33.333333% => which also means flex-basis: 33.333333% .
There is no method in flexbox to tell items in one row to line up with items in the row above — each flex line acts like a new flex container. It deals with space distribution across the main axis.
If the number of elements are within reason, it may be feasible writing css for each individual case using the quantity queries technique.
I'm over 2 years late, buuut I liked Maksym Stepanenko's idea. I originally tried a pure css solution with flexbox using:
flex-direction: column; align-content: flex-start;
but this would require you to specify a Height, and adjust the Height as needed.
So the Back-up approach using Maksym Stepanenko's idea of adjusting margin-right as needed, works with floating elements or flexbox wrapped elements, either way. I ended up correcting some of Maksym Stepanenko's calculations for this example:
JS BIN Link or code snippet below (using a library 'Detect Element Resize' so the container listener works).
/** * Detect Element Resize * * https://github.com/sdecima/javascript-detect-element-resize * Sebastian Decima * * version: 0.5.3 **/ (function () { var attachEvent = document.attachEvent, stylesCreated = false; if (!attachEvent) { var requestFrame = (function(){ var raf = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || function(fn){ return window.setTimeout(fn, 20); }; return function(fn){ return raf(fn); }; })(); var cancelFrame = (function(){ var cancel = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.clearTimeout; return function(id){ return cancel(id); }; })(); function resetTriggers(element){ var triggers = element.__resizeTriggers__, expand = triggers.firstElementChild, contract = triggers.lastElementChild, expandChild = expand.firstElementChild; contract.scrollLeft = contract.scrollWidth; contract.scrollTop = contract.scrollHeight; expandChild.style.width = expand.offsetWidth + 1 + 'px'; expandChild.style.height = expand.offsetHeight + 1 + 'px'; expand.scrollLeft = expand.scrollWidth; expand.scrollTop = expand.scrollHeight; }; function checkTriggers(element){ return element.offsetWidth != element.__resizeLast__.width || element.offsetHeight != element.__resizeLast__.height; } function scrollListener(e){ var element = this; resetTriggers(this); if (this.__resizeRAF__) cancelFrame(this.__resizeRAF__); this.__resizeRAF__ = requestFrame(function(){ if (checkTriggers(element)) { element.__resizeLast__.width = element.offsetWidth; element.__resizeLast__.height = element.offsetHeight; element.__resizeListeners__.forEach(function(fn){ fn.call(element, e); }); } }); }; /* Detect CSS Animations support to detect element display/re-attach */ var animation = false, animationstring = 'animation', keyframeprefix = '', animationstartevent = 'animationstart', domPrefixes = 'Webkit Moz O ms'.split(' '), startEvents = 'webkitAnimationStart animationstart oAnimationStart MSAnimationStart'.split(' '), pfx = ''; { var elm = document.createElement('fakeelement'); if( elm.style.animationName !== undefined ) { animation = true; } if( animation === false ) { for( var i = 0; i < domPrefixes.length; i++ ) { if( elm.style[ domPrefixes[i] + 'AnimationName' ] !== undefined ) { pfx = domPrefixes[ i ]; animationstring = pfx + 'Animation'; keyframeprefix = '-' + pfx.toLowerCase() + '-'; animationstartevent = startEvents[ i ]; animation = true; break; } } } } var animationName = 'resizeanim'; var animationKeyframes = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { opacity: 0; } to { opacity: 0; } } '; var animationStyle = keyframeprefix + 'animation: 1ms ' + animationName + '; '; } function createStyles() { if (!stylesCreated) { //opacity:0 works around a chrome bug https://code.google.com/p/chromium/issues/detail?id=286360 var css = (animationKeyframes ? animationKeyframes : '') + '.resize-triggers { ' + (animationStyle ? animationStyle : '') + 'visibility: hidden; opacity: 0; } ' + '.resize-triggers, .resize-triggers > div, .contract-trigger:before { content: \" \"; display: block; position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden; } .resize-triggers > div { background: #eee; overflow: auto; } .contract-trigger:before { width: 200%; height: 200%; }', head = document.head || document.getElementsByTagName('head')[0], style = document.createElement('style'); style.type = 'text/css'; if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } head.appendChild(style); stylesCreated = true; } } window.addResizeListener = function(element, fn){ if (attachEvent) element.attachEvent('onresize', fn); else { if (!element.__resizeTriggers__) { if (getComputedStyle(element).position == 'static') element.style.position = 'relative'; createStyles(); element.__resizeLast__ = {}; element.__resizeListeners__ = []; (element.__resizeTriggers__ = document.createElement('div')).className = 'resize-triggers'; element.__resizeTriggers__.innerHTML = '<div class="expand-trigger"><div></div></div>' + '<div class="contract-trigger"></div>'; element.appendChild(element.__resizeTriggers__); resetTriggers(element); element.addEventListener('scroll', scrollListener, true); /* Listen for a css animation to detect element display/re-attach */ animationstartevent && element.__resizeTriggers__.addEventListener(animationstartevent, function(e) { if(e.animationName == animationName) resetTriggers(element); }); } element.__resizeListeners__.push(fn); } }; window.removeResizeListener = function(element, fn){ if (attachEvent) element.detachEvent('onresize', fn); else { element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1); if (!element.__resizeListeners__.length) { element.removeEventListener('scroll', scrollListener); element.__resizeTriggers__ = !element.removeChild(element.__resizeTriggers__); } } } })();
<!doctype html> <head> <meta charset="UTF-8"> <style> .flex{ display: -webkit-flex; display: -ms-flexbox; display: flex; } .wrap{ -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap;} .cont { border: 1px solid grey; margin: auto; width: 448px; padding-right: 0; padding-bottom: 0; /*flex-direction: column;height: 225px;align-content: flex-start;*/ } .controls{ width: 500px; margin: auto; justify-content: space-around; text-align: center; } .elem { border: 1px solid blue; text-align:center; } input{ width: 25px; color: blue; } #resizableBorder{ resize: horizontal; overflow: auto; } </style> <style id="jsManipulated"> .elem { height: 100px; width: 100px; line-height: 100px; margin-right: 10px; margin-bottom: 10px; } .cont { padding-top: 10px; padding-left: 10px; } </style> </head> <body> <br> <div class="flex controls"> <button id="adder">(+) add item</button> <button id="remover">(-) remove item</button> <span> Box Size: <input id="sizer" type="text" value="100" boxCountlength="3" onkeypress='return event.charCode > 47 && event.charCode < 58'> </span> <span> Padding: <input id="padder" type="text" value="10" boxCountlength="3" > </div> <br><hr><br> <div id="resizableBorder" class="cont flex wrap"> <div class="elem"> 1</div><div class="elem"> 2</div><div class="elem"> 3</div> <div class="elem"> 4</div><div class="elem"> 5</div><div class="elem"> 6</div> <div class="elem"> 7</div><div class="elem"> 8</div><div class="elem"> 9</div> </div> <script> document.addEventListener('DOMContentLoaded', function(){ var boxCount = 9; var boxWidth = 100; var boxBorderWidth = 10; var adder = document.getElementById('adder'); var remover = document.getElementById('remover'); var sizer = document.getElementById('sizer'); var padder = document.getElementById('padder'); var flexParent = document.getElementsByClassName('cont')[0]; var styleEle = document.getElementById('jsManipulated'); var resizableBorder = document.getElementById('resizableBorder'); function addItem(){ console.log("add"); var newItem = document.createElement('div'); newItem.className += 'elem'; newItem.innerHTML = ++boxCount; flexParent.appendChild( newItem ); repaint(); } function removeItem(){ console.log("remove"); if(boxCount != 0){ --boxCount; //flexParent.removeChild(flexParent.lastChild); var position = flexParent.children.length -1; if( flexParent.children[position].className == "resize-triggers"){ --position; } //ignore resize-triggers div. flexParent.removeChild( flexParent.children[ position ] ); repaint(); } } function resizeBoxes(event) { if( event.keyCode > 47 && event.keyCode < 58){ boxWidth = this.value; } else if( event.keyCode == 40 ){ boxWidth = --this.value; } else if( event.keyCode == 38 ){ boxWidth = ++this.value; } else { console.log('non-numeric keypress ignored: '+event.keyCode); return; } updateBoxStyles( boxWidth, boxBorderWidth); } function repadBoxes(event) { if( event.keyCode > 47 && event.keyCode < 58){ boxBorderWidth= this.value; } else if( event.keyCode == 40 ){ boxBorderWidth = --this.value; } else if( event.keyCode == 38 ){ boxBorderWidth = ++this.value;} else { console.log('non-numeric keypress ignored: '+event.keyCode); return; } updateBoxStyles( boxWidth, boxBorderWidth); } function updateBoxStyles( size, edge ){ styleEle.innerHTML = ".elem { height: " +size+"px; width: " +size+"px; line-height: " +size+"px; margin-right: " +edge+"px; margin-bottom: " +edge+"px;} .cont { padding-top: " +edge+"px; padding-left: " +edge+"px;}" repaint(); } /* LISTENERS */ window.onresize = repaint; //Only needed if 'width' of container is a percentage ex: '.cont{width:60%}' adder.addEventListener('click', addItem ); remover.addEventListener('click', removeItem ); sizer.addEventListener('keyup', resizeBoxes ); padder.addEventListener('keyup', repadBoxes ); addResizeListener( resizableBorder, repaint); //using the 'detect-element-resize.js library' /** Most Important Function Called Everytime Anything Changes, in order to keep elements to the left using 'margin-right' **/ repaint(); //initial resize on pageload; function repaint() { console.log('repaint'); var elementWidth = 2- -boxWidth- -boxBorderWidth; // 2 because: border is 1px*2, '- -' because '+' causes string concatination. var elements = document.getElementsByClassName('elem'); var count = elements.length; var parentWidth = parseInt( window.getComputedStyle( flexParent).getPropertyValue('width') ); var rowsCount = Math.ceil( count / Math.floor(parentWidth/elementWidth) ); var perRow = Math.floor( count / rowsCount); var extra = count % rowsCount; for (var i=0, ele; ele = elements[i]; i++) { ele.style["margin-right"] = ""; } if( rowsCount == Infinity || rowsCount == 0){ return; }//when elementWidth < parentWidth or Zero Boxes var allPrevRowsTotal = 0; for (var i = 1; i <= rowsCount && rowsCount > 1; i++) { var perThisRow = perRow; if( extra != 0){ --extra; perThisRow++; } elements[ allPrevRowsTotal + perThisRow - 1 ].style["margin-right"] = parentWidth - (perThisRow * elementWidth) +"px"; allPrevRowsTotal += perThisRow; } } }); //https://stackoverflow.com/questions/29125444/equal-number-of-element-per-row-with-flexbox </script> </body>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With