Как вставить разрыв столбца в многостолбцовый макет CSS? - PullRequest
0 голосов
/ 25 февраля 2019

Я пытаюсь реализовать мега-меню.

Количество пунктов меню является переменным.По умолчанию они должны отображаться в 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:

enter image description here

Firefox & Safari не поддерживает правила CSS для "column-break "и отображает его как обычный пункт меню: enter image description here

Меню генерируется в JavaScript из объекта JSON, HTML можно изменить, но я предпочитаюРешение только для CSS / JS.

Есть ли у вас какие-либо идеи о том, как я могу реализовать это во всех браузерах?

Вот полный код:

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>

Ответы [ 3 ]

0 голосов
/ 01 марта 2019

Один из хакерских способов - добавить большой margin-top к следующему элементу после разрыва элемента, что заставит его начать новый столбец.

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;
    color: red;
}
.menu-item--column-break+ * {
  margin-top:100px;
}
<div class="container">
  <div class="menu">
  </div>
</div>

Единственный недостаток (который делает это взломом) заключается в том, что очень большое поле может влиять на длину столбцов:

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;
    color: red;
}
.menu-item--column-break+ * {
  margin-top:500px;
}
<div class="container">
  <div class="menu">
  </div>
</div>

Кстати, все будет работать нормально, если у вас будет разрыв в каждом столбце

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: '---cb---', type: 'column-break'},
  {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;
    color: red;
}
.menu-item--column-break+ * {
  margin-top:500px;
}
<div class="container">
  <div class="menu">
  </div>
</div>
0 голосов
/ 03 марта 2019

Я думал об этом и придумал другое решение.В основном проблема заключается в том, что разбиение на несколько столбцов не поддерживается, поэтому в настоящее время невозможно создать эти фиксированные столбцы и динамические столбцы с использованием только 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>
0 голосов
/ 28 февраля 2019

Чтобы решить эту проблему, я решил пойти с подходом JS.Это не совсем элегантно, потому что в коде js вы должны предполагать, что знаете высоту одного пункта меню.Но это решает проблему и, возможно, подойдет вашему проекту.Идея заключается в том, что я изменил отображение меню на flexbox, который помещает элементы в столбец, но переносит его в следующий столбец, когда места нет.Теперь, чтобы освободить место и обернуть, нам нужны две вещи: фиксированная высота меню (поэтому я рассчитываю ее на основе предоставленных элементов) и также невидимый элемент, который растянется до 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 ---" работать как разделители столбцов.

...