Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Equal number of element per row with flexbox?

Tags:

css

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

enter image description here

enter image description here

like image 564
Evanss Avatar asked Mar 18 '15 15:03

Evanss


People also ask

How do you display 4 items per row in flexbox?

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.

How do you get 3 items per row on flexbox?

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% .

How do you flex items in one row?

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.


2 Answers

If the number of elements are within reason, it may be feasible writing css for each individual case using the quantity queries technique.

like image 52
dalgard Avatar answered Sep 21 '22 09:09

dalgard


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>
like image 45
Nick Timmer Avatar answered Sep 20 '22 09:09

Nick Timmer