Перенос списков в столбцы

Я использую ColdFusion для заполнения шаблона, который включает неупорядоченные списки HTML <ul> .

Большинство из них не такие длинные, но некоторые из них имеют смехотворно большую длину и действительно могут быть размещены в 2-3 столбцах.

Есть ли способ сделать это легко с помощью HTML, ColdFusion или, возможно, JavaScript (я принимаю решения jQuery)? Не стоит использовать слишком сложное тяжеловесное решение, чтобы сэкономить на прокрутке.

Ответов (13)

Решение

Итак, я откопал эту статью из A List Apart CSS Swag: Multi-Column Lists . В итоге я использовал первое решение, оно не лучшее, но для других требуется либо использование сложного HTML, который не может быть сгенерирован динамически, либо создание множества настраиваемых классов, что можно было бы сделать, но для этого потребовалось бы множество встроенных стилей и возможно огромная страница.

Тем не менее, приветствуются и другие решения.

Чтобы вывести список в несколько сгруппированных тегов, вы можете выполнить цикл таким образом.

<cfset list="1,2,3,4,5,6,7,8,9,10,11,12,13,14">
<cfset numberOfColumns = "3">

<cfoutput>
<cfloop from="1" to="#numberOfColumns#" index="col">
  <ul>
  <cfloop from="#col#" to="#listLen(list)#" index="i" step="#numberOfColumns#">
    <li>#listGetAt(list,i)#</li>
  </cfloop>
  </ul>
</cfloop>
</cfoutput>

Вот еще одно решение, которое позволяет использовать столбчатые списки в следующем стиле:

1.      4.      7.       10.
2.      5.      8.       11.
3.      6.      9.       12.

(но это чистый javascript и требует jQuery, без отката)

Следующее содержит некоторый код, который изменяет прототип массива, чтобы дать новую функцию под названием «кусок», которая разбивает любой заданный массив на фрагменты заданного размера. Далее идет функция под названием buildColumns, которая принимает строку селектора UL и число, используемое для обозначения количества строк, которые могут содержать ваши столбцы. ( Вот рабочий JSFiddle )

$(document).ready(function(){
    Array.prototype.chunk = function(chunk_size){
        var array = this,
            new_array = [],
            chunk_size = chunk_size,
            i,
            length;

        for(i = 0, length = array.length; i < length; i += chunk_size){
            new_array.push(array.slice(i, i + chunk_size));
        }
        return new_array;
    }

    function buildColumns(list, row_limit) {
        var list_items = $(list).find('li').map(function(){return this;}).get(),
        row_limit = row_limit,
        columnized_list_items = list_items.chunk(row_limit);

        $(columnized_list_items).each(function(i){
            if (i != 0){
                var item_width = $(this).outerWidth(),
                    item_height = $(this).outerHeight(),
                    top_margin = -((item_height * row_limit) + (parseInt($(this).css('margin-top')) * row_limit)),
                    left_margin = (item_width * i) + (parseInt($(this).css('margin-left')) * (i + 1));

                $(this[0]).css('margin-top', top_margin);
                $(this).css('margin-left', left_margin);
            }
        });
    }

    buildColumns('ul#some_list', 5);
});

Поскольку у меня была та же проблема, и я не смог найти ничего «чистого», я подумал, что опубликовал свое решение. В этом примере я использую обратный while цикл, поэтому я могу использовать splice вместо slice . Преимущество теперь в том, что splice () нужен только индекс и диапазон, а slice () - индекс и итог. Последнее имеет тенденцию усложняться во время зацикливания.

Недостатком является то, что мне нужно перевернуть стек при добавлении.

Пример:

cols = 4; liCount = 35

цикл for с slice = [0, 9]; [9, 18]; [18, 27]; [27, 35]

перевернутое при splice = [27, 8]; [18, 9]; [9, 9]; [0, 9]

Код:

// @param (list): a jquery ul object
// @param (cols): amount of requested columns
function multiColumn (list, cols) {
    var children = list.children(),
        target = list.parent(),
        liCount = children.length,
        newUl = $("<ul />").addClass(list.prop("class")),
        newItems,
        avg = Math.floor(liCount / cols),
        rest = liCount % cols,
        take,
        stack = [];

    while (cols--) {
        take = rest > cols ? (avg + 1) : avg;
        liCount -= take;

        newItems = children.splice(liCount, take);
        stack.push(newUl.clone().append(newItems));
    }

    target.append(stack.reverse());
    list.remove();
}

Вы можете попробовать это преобразовать в cols.

CSS:

ul.col {
    width:50%;
    float:left;
}

div.clr {
    clear:both;
}

HTML-часть:

<ul class="col">
    <li>Number 1</li>
    <li>Number 2</li>

    <li>Number 19</li>
    <li>Number 20</li>
</ul>
<ul class="col">
    <li>Number 21</li>
    <li>Number 22</li>

    <li>Number 39</li>
    <li>Number 40</li>
</ul>

Flexbox можно использовать для обертывания элементов как в строках, так и в столбцах.

Основная идея - установить flex-direction на контейнере значение row или column .

NB: В настоящее время браузерная поддержка неплохая.

FIDDLE

(Пример разметки взят из этой старой статьи, посвященной отдельному списку )

ol {
  display: flex;
  flex-flow: column wrap; /* flex-direction: column */
  height: 100px; /* need to specify height :-( */
}
ol ~ ol {
  flex-flow: row wrap; /* flex-direction: row */
  max-height: auto; /* override max-height of the column direction */
}
li {
  width: 150px;
}
a {
  display: inline-block;
  padding-right: 35px;
}
<p>items in column direction</p>
<ol>
  <li><a href="#">Aloe</a>
  </li>
  <li><a href="#">Bergamot</a>
  </li>
  <li><a href="#">Calendula</a>
  </li>
  <li><a href="#">Damiana</a>
  </li>
  <li><a href="#">Elderflower</a>
  </li>
  <li><a href="#">Feverfew</a>
  </li>
  <li><a href="#">Ginger</a>
  </li>
  <li><a href="#">Hops</a>
  </li>
  <li><a href="#">Iris</a>
  </li>
  <li><a href="#">Juniper</a>
  </li>
  <li><a href="#">Kava kava</a>
  </li>
  <li><a href="#">Lavender</a>
  </li>
  <li><a href="#">Marjoram</a>
  </li>
  <li><a href="#">Nutmeg</a>
  </li>
  <li><a href="#">Oregano</a>
  </li>
  <li><a href="#">Pennyroyal</a>
  </li>
</ol>
<hr/>
<p>items in row direction</p>
<ol>
  <li><a href="#">Aloe</a>
  </li>
  <li><a href="#">Bergamot</a>
  </li>
  <li><a href="#">Calendula</a>
  </li>
  <li><a href="#">Damiana</a>
  </li>
  <li><a href="#">Elderflower</a>
  </li>
  <li><a href="#">Feverfew</a>
  </li>
  <li><a href="#">Ginger</a>
  </li>
  <li><a href="#">Hops</a>
  </li>
  <li><a href="#">Iris</a>
  </li>
  <li><a href="#">Juniper</a>
  </li>
  <li><a href="#">Kava kava</a>
  </li>
  <li><a href="#">Lavender</a>
  </li>
  <li><a href="#">Marjoram</a>
  </li>
  <li><a href="#">Nutmeg</a>
  </li>
  <li><a href="#">Oregano</a>
  </li>
  <li><a href="#">Pennyroyal</a>
  </li>
</ol>

Вот вариант примера Thumbkin (с использованием JQuery):

var $cat_list = $('ul#catList'); // UL with all list items.
var $cat_flow = $('div#catFlow'); // Target div.
var $cat_list_clone = $cat_list.clone(); // Clone the list.
$('li:odd', $cat_list).remove(); // Remove odd list items.
$('li:even', $cat_list_clone).remove(); // Remove even list items.
$cat_flow.append($cat_list_clone); // Append the duplicate to the target div.

Спасибо Thumbkin!

Насколько мне известно, для этого не существует чистого CSS / HTML способа. Лучше всего сделать это в предварительной обработке ( if list length > 150, split into 3 columns, else if > 70, split into 2 columns, else 1 ).

Другой вариант с использованием JavaScript (я не знаком конкретно с библиотекой jQuery ) - перебирать списки, вероятно, на основе того, что они являются определенным классом, подсчитывать количество дочерних элементов и, если это достаточно большое число, динамически создать новый список после первого, перенеся некоторое количество элементов списка в новый список. Что касается реализации столбцов, вы, вероятно, могли бы разместить их влево, а затем элемент со стилем clear: left или clear: both .

.column {
  float: left;
  width: 50%;
}
.clear {
  clear: both;
}
<ul class="column">
  <li>Item 1</li>
  <li>Item 2</li>
  <!-- ... -->
  <li>Item 49</li>
  <li>Item 50</li>
</ul>
<ul class="column">
  <li>Item 51</li>
  <li>Item 52</li>
  <!-- ... -->
  <li>Item 99</li>
  <li>Item 100</li>
</ul>
<div class="clear">

Если поддержка Safari и Firefox вам подходит, есть решение CSS:

ul {
  -webkit-column-count: 3;
     -moz-column-count: 3;
          column-count: 3;
  -webkit-column-gap: 2em;
     -moz-column-gap: 2em;
          column-gap: 2em;
}

Насчет Opera я не уверен.

Следующий код JavaScript работает только в Spidermonkey и Rhino, и он работает на узлах E4X - то есть это полезно только для серверного JavaScript, но может дать кому-то отправную точку для создания версии jQuery. (Это было очень полезно для меня на стороне сервера, но мне это не нужно было на клиенте настолько сильно, чтобы на самом деле это построить.)

function columns(x,num) {
    num || (num = 2);
    x.normalize();

    var cols, i, j, col, used, left, len, islist;
    used = left = 0;
    cols = <div class={'columns cols'+num}></div>;

    if((left = x.length())==1)
        left = x.children().length();
    else
        islist = true;

    for(i=0; i<num; i++) {
        len = Math.ceil(left/(num-i));
        col = islist ? new XMLList
                     : <{x.name()}></{x.name()}>;

        if(!islist && x['@class'].toString())
            col['@class'] = x['@class'];

        for(j=used; j<len+used; j++)
            islist ? (col += x[j].copy()) 
                   : (col.appendChild(x.child(j).copy()));

        used += len;
        left -= len;
        cols.appendChild(<div class={'column'+(i==(num-1) ? 'collast' : '')}>{col}</div>);
    }
    return cols;
}

Вы называете это как columns(listNode,2) две колонки, а получается:

<ul class="foo">
  <li>a</li>
  <li>b</li>
  <li>c</li>
</ul>

в:

<div class="columns cols2">
  <div class="column">
    <ul class="foo">
      <li>a</li>
      <li>b</li>
    </ul>
  </div>
  <div class="column collast">
    <ul class="foo">
      <li>c</li>
    </ul>
  </div>
</div>

Он предназначен для использования с CSS следующим образом:

div.columns {
    overflow: hidden;
    _zoom: 1;
}

div.columns div.column {
    float: left;
}

div.cols2 div.column {
    width: 47.2%;
    padding: 0 5% 0 0;
}

div.cols3 div.column {
    width: 29.8%;
    padding: 0 5% 0 0;
}

div.cols4 div.column {
    width: 21.1%;
    padding: 0 5% 0 0;
}

div.cols5 div.column {
    width: 15.9%;
    padding: 0 5% 0 0;
}

div.columns div.collast {
    padding: 0;
}

Большинство людей забывают, что при перемещении <li/> элементов все элементы должны быть одинаковой высоты, иначе столбцы начинают выходить из строя.

Поскольку вы используете язык на стороне сервера, я бы рекомендовал использовать CF для разделения списка на 3 массива. Затем вы можете использовать внешний, ul чтобы обернуть 3 внутренних ul следующим образом:

<cfset thelist = "1,2,3,4,5,6,7,8,9,10,11,12,13">  
<cfset container = []>  
<cfset container[1] = []>  
<cfset container[2] = []>  
<cfset container[3] = []>  

<cfloop list="#thelist#" index="i">  
    <cfif i mod 3 eq 0>  
        <cfset arrayappend(container[3], i)>  
    <cfelseif i mod 2 eq 0>  
        <cfset arrayappend(container[2], i)>  
    <cfelse>  
        <cfset arrayappend(container[1], i)>  
    </cfif>  
</cfloop>  

<style type="text/css"> 
    ul li { float: left; }  
    ul li ul li { clear: left; }  
</style>  

<cfoutput>  
<ul>  
    <cfloop from="1" to="3" index="a">  
    <li>  
        <ul>  
            <cfloop array="#container[a]#" index="i">  
            <li>#i#</li>  
            </cfloop>  
        </ul>  
    </li>  
    </cfloop>  
</ul>  
</cfoutput>

Я сделал это с помощью jQuery - он кроссплатформенный и требует минимум кода.

Выберите UL, клонируйте его и вставьте после предыдущего UL. Что-то вроде:

$("ul#listname").clone().attr("id","listname2").after()

Это вставит копию вашего списка после предыдущего. Если исходный список оформлен с помощью float: left, они должны отображаться рядом.

Затем вы можете удалить четные элементы из левого списка и нечетные элементы из правого списка.

$("ul#listname li:even").remove();
$("ul#listname2 li:odd").remove();

Теперь у вас есть список из двух столбцов слева направо.

Чтобы сделать больше столбцов, вы захотите использовать .slice(begin,end) и / или :nth-child селектор. т.е. для 21 LI вы можете .slice(8,14) создать новый UL, вставленный после вашего исходного UL, затем выбрать исходный UL и удалить выбранные li с помощью ul :gt(8) .

Попробуйте книгу Бибо / Каца о jQuery, это отличный ресурс.

Используя операцию по модулю, вы можете быстро разделить свой список на несколько списков, вставив </ul><ul> во время цикла.

<cfset numberOfColumns = 3 />
<cfset numberOfEntries = 34 />
<ul style="float:left;">
    <cfloop from="1" to="#numberOfEntries#" index="i">
        <li>#i#</li>
            <cfif NOT i MOD ceiling(numberOfEntries / numberOfColumns)>
                </ul>
                <ul style="float:left;">
            </cfif>
    </cfloop>
</ul>

Используйте ceiling() вместо, round() чтобы убедиться, что у вас нет лишних значений в конце списка и что последний столбец является самым коротким.