Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I insert column break in a CSS multi-column layout?

I'm trying to implement a mega-menu.

The number of menu items is variable. By default, they have to be rendered in 4 columns, balanced (the number of items on each column should be nearly the same as the other columns). The height of the mega-menu is also variable, based on its content.

I've implemented it with CSS Multi-Column layout.

The code for this is:

.menu {
  -webkit-column-count: 4;
     -moz-column-count: 4;
          column-count: 4;
 -webkit-column-gap: 32px;
    -moz-column-gap: 32px;
         column-gap: 32px;
}

My issue is that there is a special menu item type, that should act as a column break. This menu item type is optional, but if present, it should force the browser to start a new column to display the content (there can be max 3 column breaks).

I've added the following css code:

.menu-item--column-break {
    display: block;
    -webkit-column-break-before: column;
              -moz-break-before: column;
                   break-before: column;
}

But this CSS works only on Chrome:

enter image description here

Firefox & Safari does not support the CSS rules for the "column-break" element and displays it like a normal menu item: enter image description here

The menu is generated in JavaScript from a JSON object, the HTML can be altered, but I prefer a CSS/JS-only solution.

Do you have any idea on how could I implement this in all browsers?

Here's the full code:

https://codepen.io/andreivictor/pen/ywLJKx

or

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>
like image 729
andreivictor Avatar asked Feb 25 '19 19:02

andreivictor


People also ask

What are the CSS properties that are used to manage column breaks?

The break-inside property specifies whether or not a page break, column break, or region break should occur inside the specified element. The break-inside property extends then CSS2 page-break-inside property.

Can I use CSS multi-column?

CSS Multi-column Layout is a module of CSS that adds support for multi-column layouts.

What is column-gap CSS?

The CSS column-gap property sets space (also called “gutters”) between between columns in CSS Grid, Flexbox, and CSS Columns layouts.


1 Answers

To solve this I decided to go with js approach. It is not fully elegant because in the js code you need to assume you know the height of a single menu item. But this solves the issue and maybe it will fit your project. The idea is that I changed the display of the menu into a flexbox that places items in a column but wraps into the next column when there is no space. Now, to be able to run out of space and wrap we need two things: a fixed menu height (so that is why I calculate it based on the items provided) and also an invisible element that would stretch to 100% height (it does not fit to the current nor the next column so it creates a 0px-wide column on its own thus acting as a column separator). Take a look at this solution:

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>

Oh, one more thing. I was not sure if you actually want to display the "---cb---" items or not. I left them displayed in the solution but if you want to get rid of them, you can easily modify the code by deleting my extra column dividers, and instead make your "---cb---" items work as the column dividers.

like image 183
Jakub Rusilko Avatar answered Nov 08 '22 20:11

Jakub Rusilko