如何在 CSS 多栏布局中插入分栏符?
How can I insert column break in a CSS multi-column layout?
我正在尝试实现一个大型菜单。
菜单项的数量是可变的。默认情况下,它们必须以 4 列 平衡呈现(每列上的项目数应与其他列几乎相同)。
大型菜单的高度也是可变的,具体取决于其内容。
我已经用 CSS 多列布局 实现了它。
代码是:
.menu {
-webkit-column-count: 4;
-moz-column-count: 4;
column-count: 4;
-webkit-column-gap: 32px;
-moz-column-gap: 32px;
column-gap: 32px;
}
我的问题是有一个特殊的菜单项类型,它应该充当 分栏符。此菜单项类型是可选的,但如果存在,它应该强制浏览器开始一个新列来显示内容(最多可以有 3 个分栏符)。
我添加了以下 css 代码:
.menu-item--column-break {
display: block;
-webkit-column-break-before: column;
-moz-break-before: column;
break-before: column;
}
但是这个 CSS 只适用于 Chrome:
Firefox 和 Safari 不支持 "column-break" 元素的 CSS 规则,并像普通菜单项一样显示它:
菜单是在 JavaScript 中从 JSON 对象生成的,HTML 可以更改,但我更喜欢 CSS/JS-only 解决方案。
你知道我如何在所有浏览器中实现它吗?
完整代码如下:
https://codepen.io/andreivictor/pen/ywLJKx
或
let items = [
{title: 'Category 1', type: 'menu-item'},
{title: 'Category 2', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 3', type: 'menu-item'},
{title: 'Category 4', type: 'menu-item'},
{title: 'Category 5', type: 'menu-item'},
{title: 'Category 6', type: 'menu-item'},
{title: 'Category 7', type: 'menu-item'},
{title: 'Category 8', type: 'menu-item'},
{title: 'Category 9', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 10', type: 'menu-item'},
{title: 'Category 11', type: 'menu-item'},
{title: 'Category 12', type: 'menu-item'},
{title: 'Category 13', type: 'menu-item'},
{title: 'Category 14', type: 'menu-item'},
{title: 'Category 15', type: 'menu-item'},
{title: 'Category 16', type: 'menu-item'},
{title: 'Category 17', type: 'menu-item'},
{title: 'Category 18', type: 'menu-item'},
{title: 'Category 19', type: 'menu-item'},
{title: 'Category 20', type: 'menu-item'},
{title: 'Category 21', type: 'menu-item'},
];
const $menu = document.querySelector('.menu');
console.log( $menu );
items.forEach((item) => {
let nodeItem = document.createElement("div");
nodeItem.classList.add('menu-item');
let nodeItemText = document.createTextNode(item.title);
nodeItem.appendChild(nodeItemText);
if (item.type === 'column-break') {
nodeItem.classList.add('menu-item--column-break');
}
$menu.appendChild(nodeItem);
});
.menu {
position: relative;
padding: 0 16px;
-webkit-column-count: 4;
-moz-column-count: 4;
column-count: 4;
-moz-column-rule: 1px solid #e2e1e1;
column-rule: 1px solid #e2e1e1;
-webkit-column-gap: 32px;
-moz-column-gap: 32px;
column-gap: 32px;
}
.menu-item--column-break {
display: block;
-webkit-column-break-after: column;
-moz-break-after: column;
break-after: column;
color: red;
}
<div class="container">
<div class="menu">
</div>
</div>
为了解决这个问题,我决定采用 js 方法。它并不完全优雅,因为在 js 代码中你需要假设你知道单个菜单项的高度。但这解决了问题,也许它适合您的项目。这个想法是我将菜单的显示更改为一个 flexbox,它将项目放在一列中,但在没有 space 时换行到下一列。现在,为了能够从 space 中 运行 并换行,我们需要两件事:一个固定的菜单高度(这就是我根据提供的项目计算它的原因)以及一个不可见的元素拉伸到 100% 高度(它不适合当前列或下一列,因此它自己创建一个 0px 宽的列,从而充当列分隔符)。看看这个解决方案:
let items = [
{title: 'Category 1', type: 'menu-item'},
{title: 'Category 2', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 3', type: 'menu-item'},
{title: 'Category 4', type: 'menu-item'},
{title: 'Category 5', type: 'menu-item'},
{title: 'Category 6', type: 'menu-item'},
{title: 'Category 7', type: 'menu-item'},
{title: 'Category 8', type: 'menu-item'},
{title: 'Category 9', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 10', type: 'menu-item'},
{title: 'Category 11', type: 'menu-item'},
{title: 'Category 12', type: 'menu-item'},
{title: 'Category 13', type: 'menu-item'},
{title: 'Category 14', type: 'menu-item'},
{title: 'Category 15', type: 'menu-item'},
{title: 'Category 16', type: 'menu-item'},
{title: 'Category 17', type: 'menu-item'},
{title: 'Category 18', type: 'menu-item'},
{title: 'Category 19', type: 'menu-item'},
{title: 'Category 20', type: 'menu-item'},
{title: 'Category 21', type: 'menu-item'},
];
const $menu = document.querySelector('.menu');
console.log( $menu );
var longestColumnLength = 0;
var currentColumnLength = 0;
var numberOfBreaks = 0;
items.forEach((item) => {
currentColumnLength++;
let nodeItem = document.createElement("div");
nodeItem.classList.add('menu-item');
let nodeItemText = document.createTextNode(item.title);
nodeItem.appendChild(nodeItemText);
if (item.type === 'column-break') {
nodeItem.classList.add('menu-item--column-break');
let breaker = document.createElement("div");
breaker.classList.add('menu-item--column-break-line');
$menu.appendChild(nodeItem);
$menu.appendChild(breaker);
longestColumnLength = Math.max(longestColumnLength,
currentColumnLength);
currentColumnLength = 0;
numberOfBreaks++;
} else {
$menu.appendChild(nodeItem);
}
});
var availableNaturalColumnsAtTheEnd = Math.max(1, 4 - numberOfBreaks);
var maxLengthOfRemainingItems = currentColumnLength /
availableNaturalColumnsAtTheEnd;
var actualLongestColumn = Math.max(longestColumnLength,
maxLengthOfRemainingItems)
$menu.setAttribute("style", "height: " + actualLongestColumn*20 + "px")
.menu {
position: relative;
padding: 0 16px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 200px;
}
.menu-item{
height: 20px;
}
.menu-item--column-break {
display: block;
color: red;
}
.menu-item--column-break-line {
height: 100%;
width: 0;
overflow: hidden;
}
<div class="container">
<div class="menu">
</div>
</div>
哦,还有一件事。我不确定您是否真的想显示“---cb---”项目。我让它们显示在解决方案中,但如果你想摆脱它们,你可以通过删除我额外的列分隔符轻松修改代码,而不是让你的“---cb---”项目作为列分隔符。
我在考虑这个问题并想出了另一个解决方案。基本上问题是不支持多列中断,因此目前无法为所有浏览器创建那些仅 css 的固定列和动态列。
因此,我决定将问题一分为二。我根据固定的休息时间将项目分成几组。我假设每个组都将是初学者的一个专栏。然后我看看我有多少列。如果它小于 4(你想要的列数),那么我允许最大的一组动态地分成一列。我继续这样做,直到我达到 4 列的总数 - 无论是固定的,还是动态的,或两者兼而有之。
请参阅下面的代码段。
此外,通过添加、删除或移动中断来播放片段。它应该适用于许多不同的场景。
let items = [
{title: 'Category 1', type: 'menu-item'},
{title: 'Category 2', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 3', type: 'menu-item'},
{title: 'Category 4', type: 'menu-item'},
{title: 'Category 5', type: 'menu-item'},
{title: 'Category 6', type: 'menu-item'},
{title: 'Category 7', type: 'menu-item'},
{title: 'Category 8', type: 'menu-item'},
{title: 'Category 9', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 10', type: 'menu-item'},
{title: 'Category 11', type: 'menu-item'},
{title: 'Category 12', type: 'menu-item'},
{title: 'Category 13', type: 'menu-item'},
{title: 'Category 14', type: 'menu-item'},
{title: 'Category 15', type: 'menu-item'},
//{title: '---cb---', type: 'column-break'},
{title: 'Category 16', type: 'menu-item'},
{title: 'Category 17', type: 'menu-item'},
{title: 'Category 18', type: 'menu-item'},
{title: 'Category 19', type: 'menu-item'},
{title: 'Category 20', type: 'menu-item'},
{title: 'Category 21', type: 'menu-item'}
];
const $menu = document.querySelector('.menu');
var allGroups = [];
var currentGroup = 0;
allGroups.push({ items: [], columns: 1});
function addGroup($menu, group, numberOfColumns){
let columnItem = document.createElement("div");
columnItem.classList.add('menu-group');
if(numberOfColumns === 1){
columnItem.classList.add('fixed');
} else {
columnItem.classList.add('dynamic-columns');
var style = '-webkit-column-count: ' + numberOfColumns + ';';
style += '-moz-column-count: ' + numberOfColumns + ';';
style += 'column-count: ' + numberOfColumns + ';';
columnItem.setAttribute('style', style);
}
group.forEach((groupItem) => {
columnItem.appendChild(groupItem);
});
$menu.appendChild(columnItem);
};
var columnsCount = 1;
items.forEach((item) => {
let nodeItem = document.createElement("div");
allGroups[currentGroup].items.push(nodeItem);
nodeItem.classList.add('menu-item');
let nodeItemText = document.createTextNode(item.title);
nodeItem.appendChild(nodeItemText);
if (item.type === 'column-break') {
nodeItem.classList.add('menu-item--column-break');
//addGroup($menu, currentGroup, 1);
currentGroup++;
allGroups.push({ items: [], columns: 1});
columnsCount++;
}
});
var forSorting = [];
allGroups.forEach((item) => { forSorting.push(item); });
while(columnsCount < 4){
forSorting.sort(function(a, b){
return (b.items.length/b.columns) - (a.items.length/a.columns);
});
forSorting[0].columns++;
columnsCount++;
}
allGroups.forEach((item) => {
addGroup($menu, item.items, item.columns);
});
.menu {
position: relative;
padding: 0 16px;
display: flex;
flex-direction: row;
}
.menu-group:not(:last-child){
border-right: 1px solid #e2e1e1;
margin-right: 8px;
}
.menu-group.fixed {
flex-basis: calc(25% - 8px);
flex-grow: 0;
flex-shrink: 0;
}
.menu-group.dynamic-columns {
flex-grow: 1;
-moz-column-rule: 1px solid #e2e1e1;
column-rule: 1px solid #e2e1e1;
}
.menu-item--column-break {
display: block;
color: red;
}
<div class="container">
<div class="menu">
</div>
</div>
我正在尝试实现一个大型菜单。
菜单项的数量是可变的。默认情况下,它们必须以 4 列 平衡呈现(每列上的项目数应与其他列几乎相同)。 大型菜单的高度也是可变的,具体取决于其内容。
我已经用 CSS 多列布局 实现了它。
代码是:
.menu {
-webkit-column-count: 4;
-moz-column-count: 4;
column-count: 4;
-webkit-column-gap: 32px;
-moz-column-gap: 32px;
column-gap: 32px;
}
我的问题是有一个特殊的菜单项类型,它应该充当 分栏符。此菜单项类型是可选的,但如果存在,它应该强制浏览器开始一个新列来显示内容(最多可以有 3 个分栏符)。
我添加了以下 css 代码:
.menu-item--column-break {
display: block;
-webkit-column-break-before: column;
-moz-break-before: column;
break-before: column;
}
但是这个 CSS 只适用于 Chrome:
Firefox 和 Safari 不支持 "column-break" 元素的 CSS 规则,并像普通菜单项一样显示它:
菜单是在 JavaScript 中从 JSON 对象生成的,HTML 可以更改,但我更喜欢 CSS/JS-only 解决方案。
你知道我如何在所有浏览器中实现它吗?
完整代码如下:
https://codepen.io/andreivictor/pen/ywLJKx
或
let items = [
{title: 'Category 1', type: 'menu-item'},
{title: 'Category 2', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 3', type: 'menu-item'},
{title: 'Category 4', type: 'menu-item'},
{title: 'Category 5', type: 'menu-item'},
{title: 'Category 6', type: 'menu-item'},
{title: 'Category 7', type: 'menu-item'},
{title: 'Category 8', type: 'menu-item'},
{title: 'Category 9', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 10', type: 'menu-item'},
{title: 'Category 11', type: 'menu-item'},
{title: 'Category 12', type: 'menu-item'},
{title: 'Category 13', type: 'menu-item'},
{title: 'Category 14', type: 'menu-item'},
{title: 'Category 15', type: 'menu-item'},
{title: 'Category 16', type: 'menu-item'},
{title: 'Category 17', type: 'menu-item'},
{title: 'Category 18', type: 'menu-item'},
{title: 'Category 19', type: 'menu-item'},
{title: 'Category 20', type: 'menu-item'},
{title: 'Category 21', type: 'menu-item'},
];
const $menu = document.querySelector('.menu');
console.log( $menu );
items.forEach((item) => {
let nodeItem = document.createElement("div");
nodeItem.classList.add('menu-item');
let nodeItemText = document.createTextNode(item.title);
nodeItem.appendChild(nodeItemText);
if (item.type === 'column-break') {
nodeItem.classList.add('menu-item--column-break');
}
$menu.appendChild(nodeItem);
});
.menu {
position: relative;
padding: 0 16px;
-webkit-column-count: 4;
-moz-column-count: 4;
column-count: 4;
-moz-column-rule: 1px solid #e2e1e1;
column-rule: 1px solid #e2e1e1;
-webkit-column-gap: 32px;
-moz-column-gap: 32px;
column-gap: 32px;
}
.menu-item--column-break {
display: block;
-webkit-column-break-after: column;
-moz-break-after: column;
break-after: column;
color: red;
}
<div class="container">
<div class="menu">
</div>
</div>
为了解决这个问题,我决定采用 js 方法。它并不完全优雅,因为在 js 代码中你需要假设你知道单个菜单项的高度。但这解决了问题,也许它适合您的项目。这个想法是我将菜单的显示更改为一个 flexbox,它将项目放在一列中,但在没有 space 时换行到下一列。现在,为了能够从 space 中 运行 并换行,我们需要两件事:一个固定的菜单高度(这就是我根据提供的项目计算它的原因)以及一个不可见的元素拉伸到 100% 高度(它不适合当前列或下一列,因此它自己创建一个 0px 宽的列,从而充当列分隔符)。看看这个解决方案:
let items = [
{title: 'Category 1', type: 'menu-item'},
{title: 'Category 2', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 3', type: 'menu-item'},
{title: 'Category 4', type: 'menu-item'},
{title: 'Category 5', type: 'menu-item'},
{title: 'Category 6', type: 'menu-item'},
{title: 'Category 7', type: 'menu-item'},
{title: 'Category 8', type: 'menu-item'},
{title: 'Category 9', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 10', type: 'menu-item'},
{title: 'Category 11', type: 'menu-item'},
{title: 'Category 12', type: 'menu-item'},
{title: 'Category 13', type: 'menu-item'},
{title: 'Category 14', type: 'menu-item'},
{title: 'Category 15', type: 'menu-item'},
{title: 'Category 16', type: 'menu-item'},
{title: 'Category 17', type: 'menu-item'},
{title: 'Category 18', type: 'menu-item'},
{title: 'Category 19', type: 'menu-item'},
{title: 'Category 20', type: 'menu-item'},
{title: 'Category 21', type: 'menu-item'},
];
const $menu = document.querySelector('.menu');
console.log( $menu );
var longestColumnLength = 0;
var currentColumnLength = 0;
var numberOfBreaks = 0;
items.forEach((item) => {
currentColumnLength++;
let nodeItem = document.createElement("div");
nodeItem.classList.add('menu-item');
let nodeItemText = document.createTextNode(item.title);
nodeItem.appendChild(nodeItemText);
if (item.type === 'column-break') {
nodeItem.classList.add('menu-item--column-break');
let breaker = document.createElement("div");
breaker.classList.add('menu-item--column-break-line');
$menu.appendChild(nodeItem);
$menu.appendChild(breaker);
longestColumnLength = Math.max(longestColumnLength,
currentColumnLength);
currentColumnLength = 0;
numberOfBreaks++;
} else {
$menu.appendChild(nodeItem);
}
});
var availableNaturalColumnsAtTheEnd = Math.max(1, 4 - numberOfBreaks);
var maxLengthOfRemainingItems = currentColumnLength /
availableNaturalColumnsAtTheEnd;
var actualLongestColumn = Math.max(longestColumnLength,
maxLengthOfRemainingItems)
$menu.setAttribute("style", "height: " + actualLongestColumn*20 + "px")
.menu {
position: relative;
padding: 0 16px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
height: 200px;
}
.menu-item{
height: 20px;
}
.menu-item--column-break {
display: block;
color: red;
}
.menu-item--column-break-line {
height: 100%;
width: 0;
overflow: hidden;
}
<div class="container">
<div class="menu">
</div>
</div>
哦,还有一件事。我不确定您是否真的想显示“---cb---”项目。我让它们显示在解决方案中,但如果你想摆脱它们,你可以通过删除我额外的列分隔符轻松修改代码,而不是让你的“---cb---”项目作为列分隔符。
我在考虑这个问题并想出了另一个解决方案。基本上问题是不支持多列中断,因此目前无法为所有浏览器创建那些仅 css 的固定列和动态列。 因此,我决定将问题一分为二。我根据固定的休息时间将项目分成几组。我假设每个组都将是初学者的一个专栏。然后我看看我有多少列。如果它小于 4(你想要的列数),那么我允许最大的一组动态地分成一列。我继续这样做,直到我达到 4 列的总数 - 无论是固定的,还是动态的,或两者兼而有之。
请参阅下面的代码段。
此外,通过添加、删除或移动中断来播放片段。它应该适用于许多不同的场景。
let items = [
{title: 'Category 1', type: 'menu-item'},
{title: 'Category 2', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 3', type: 'menu-item'},
{title: 'Category 4', type: 'menu-item'},
{title: 'Category 5', type: 'menu-item'},
{title: 'Category 6', type: 'menu-item'},
{title: 'Category 7', type: 'menu-item'},
{title: 'Category 8', type: 'menu-item'},
{title: 'Category 9', type: 'menu-item'},
{title: '---cb---', type: 'column-break'},
{title: 'Category 10', type: 'menu-item'},
{title: 'Category 11', type: 'menu-item'},
{title: 'Category 12', type: 'menu-item'},
{title: 'Category 13', type: 'menu-item'},
{title: 'Category 14', type: 'menu-item'},
{title: 'Category 15', type: 'menu-item'},
//{title: '---cb---', type: 'column-break'},
{title: 'Category 16', type: 'menu-item'},
{title: 'Category 17', type: 'menu-item'},
{title: 'Category 18', type: 'menu-item'},
{title: 'Category 19', type: 'menu-item'},
{title: 'Category 20', type: 'menu-item'},
{title: 'Category 21', type: 'menu-item'}
];
const $menu = document.querySelector('.menu');
var allGroups = [];
var currentGroup = 0;
allGroups.push({ items: [], columns: 1});
function addGroup($menu, group, numberOfColumns){
let columnItem = document.createElement("div");
columnItem.classList.add('menu-group');
if(numberOfColumns === 1){
columnItem.classList.add('fixed');
} else {
columnItem.classList.add('dynamic-columns');
var style = '-webkit-column-count: ' + numberOfColumns + ';';
style += '-moz-column-count: ' + numberOfColumns + ';';
style += 'column-count: ' + numberOfColumns + ';';
columnItem.setAttribute('style', style);
}
group.forEach((groupItem) => {
columnItem.appendChild(groupItem);
});
$menu.appendChild(columnItem);
};
var columnsCount = 1;
items.forEach((item) => {
let nodeItem = document.createElement("div");
allGroups[currentGroup].items.push(nodeItem);
nodeItem.classList.add('menu-item');
let nodeItemText = document.createTextNode(item.title);
nodeItem.appendChild(nodeItemText);
if (item.type === 'column-break') {
nodeItem.classList.add('menu-item--column-break');
//addGroup($menu, currentGroup, 1);
currentGroup++;
allGroups.push({ items: [], columns: 1});
columnsCount++;
}
});
var forSorting = [];
allGroups.forEach((item) => { forSorting.push(item); });
while(columnsCount < 4){
forSorting.sort(function(a, b){
return (b.items.length/b.columns) - (a.items.length/a.columns);
});
forSorting[0].columns++;
columnsCount++;
}
allGroups.forEach((item) => {
addGroup($menu, item.items, item.columns);
});
.menu {
position: relative;
padding: 0 16px;
display: flex;
flex-direction: row;
}
.menu-group:not(:last-child){
border-right: 1px solid #e2e1e1;
margin-right: 8px;
}
.menu-group.fixed {
flex-basis: calc(25% - 8px);
flex-grow: 0;
flex-shrink: 0;
}
.menu-group.dynamic-columns {
flex-grow: 1;
-moz-column-rule: 1px solid #e2e1e1;
column-rule: 1px solid #e2e1e1;
}
.menu-item--column-break {
display: block;
color: red;
}
<div class="container">
<div class="menu">
</div>
</div>