【问题标题】:Equal number of element per row with flexbox?flexbox每行的元素数量相等吗?
【发布时间】:2015-05-21 10:29:32
【问题描述】:

我有动态内容和响应式布局,因此项目数量和可用宽度会有所不同。有时 div 中的元素需要换行到第二个“行”。

使用 flexbox(或任何其他 CSS 方法)可以使每行上的项目数相等吗?

<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

【问题讨论】:

  • 您是否建议对不同的屏幕尺寸使用媒体查询?
  • 我的内容是动态的。我不知道有多少元素,所以我认为媒体查询不会起作用。
  • 鉴于 7 是质数,您将如何确定如何准确分配弹性项目?想想吧。
  • 如果有奇数,是否有智能解释?请参阅我的问题中的图片。
  • 我认为css columns 会有所帮助

标签: css


【解决方案1】:

我迟到了 2 年多,但我喜欢 Maksym Stepanenko 的想法。 我最初尝试使用 flexbox 的纯 css 解决方案:

flex-direction: column;
align-content: flex-start;

但这需要您指定一个高度,并根据需要调整高度。

因此,使用 Maksym Stepanenko 根据需要调整边距的想法的备份方法适用于浮动元素或 flexbox 包装元素,无论哪种方式。 我最终为这个例子纠正了一些 Maksym Stepanenko 的计算:

JS BIN Link 或下面的代码 sn-p(使用库“检测元素调整大小”以便容器侦听器工作)。

/**
* 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>

【讨论】:

    【解决方案2】:

    正如 dalgard 所说,如果您想要 CSS 解决方案,数量查询是一种解决方案。

    但是,您将需要编写几个案例

    让我们看看它适用于七个元素的情况

    .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: 500px;
      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;
    }
    
    .elem:nth-last-child(7):first-child {
      border-color: red;
      }
    .elem:nth-last-child(7):first-child ~ .elem:nth-child(3) {
      border-color: green;
      margin-right: 100px;
      }
    <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>
    <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 class="elem"></div>
    </div>

    我设置红色边框规则只是为了表明它适用于七元素情况,但不适用于其他情况。然后,在此之后的第三个元素旨在使行完成(并以不同的边框颜色显示以使其可见

    【讨论】:

    • 这是否意味着我需要根据元素的数量使用不同的 CSS?由于我的内容是动态的,这对我来说是不可能的。
    【解决方案3】:

    虽然您要求 Css 解决方案,但我找不到,所以我创建了一个基于 jquery 的解决方案(我认为可以优化很多)。

    此解决方案仅在您知道项目宽度并且它是静态的(如您的示例中)时才有效(当前112pxbordermargin)。

    希望这能满足您的需求。

    Css和HTML不变

    Javascript

    $(window).resize(function () {
        var elementWidth = 112;
        var Elements = $(".elem");
        var parentWidth = $(".cont").width();
        var rowsCount = Math.ceil((Elements.length * elementWidth) / parentWidth);
        var perRow = Math.ceil(Elements.length / rowsCount);
        perRow = perRow * elementWidth > parentWidth ? perRow - 1 : perRow;
        $.each(Elements, function (index, item) {
            $(this).css("margin-right", "");
        });
    
        for (i = 1; i <= rowsCount && rowsCount > 1; i++) {
            if (i * perRow <= Elements.length) Elements.eq((i * perRow) - 1).css("margin-right", parentWidth - (perRow * elementWidth));
        }
    });
    

    这是Fiddle

    注意

    这尚未在所有屏幕尺寸和不同数量的元素上进行测试, 只是几个案例,所以我可能会在某个时候失败。

    【讨论】:

      【解决方案4】:

      如果元素的数量在合理范围内,使用quantity queries 技术为每个单独的案例编写css 可能是可行的。

      【讨论】:

      • 我认为这对我来说是不可能的。我用新图片更新了我的问题。
      • 多么棒的资源!
      猜你喜欢
      • 2014-09-23
      • 1970-01-01
      • 2020-03-23
      • 1970-01-01
      • 2012-11-09
      • 1970-01-01
      • 2021-06-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多