Den*_*ang 4 javascript css sorting listitem flexbox
假设我有一组这样的列表项(某种类别):
ul.categoryList {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 180px;
}
ul.categoryList > li {
list-style: none;
}Run Code Online (Sandbox Code Playgroud)
<ul class="categoryList">
<li>Literature</li>
<li>Science Fiction and Fantasy</li>
<li>Harry Potter</li>
<li>Movies and Films</li>
<li>Books</li>
</ul>Run Code Online (Sandbox Code Playgroud)
这<ul>是在 a 内<div>,max-width如果调整窗口大小或在不同的分辨率/设备(手机、平板电脑等)上, a 可以更改。
正如您所看到的,某些列表项比其他项长。假设此容器<ul>只能包含列表项Science Fiction and Fantasy和更多内容,因此下一项将转到下一行,因为它不适合同一行。
正如您可能看到的那样,问题在于Literature和Books可能位于同一行,但由于它们不连续,因此它们最终会出现在单独的行上,这同样适用于其他项目。
因此,我没有将一些最短的项目放在一起以消耗更少的行,而是得到 5 行(实际上每个项目一个),这很消耗空间。
有没有什么办法解决这一问题?可以只用 CSS 来完成还是需要 JavaScript 来完成?
您需要使用 JavaScript 根据元素的长度/大小对元素进行排序。
\n\n这是一个基本示例,用于Array.prototype.sort()根据每个字符的字符数对它们进行排序 ( Node.innerText):
// Sort the elements according to their number of characters:\r\n\r\nconst categoryList = document.getElementById(\'categoryList\');\r\n\r\nArray.from(categoryList.children).sort((a, b) => {\r\n const charactersA = a.innerText.length;\r\n const charactersB = b.innerText.length;\r\n \r\n if (charactersA < charactersB) {\r\n return -1;\r\n } else if (charactersA === charactersB) {\r\n return 0;\r\n } else {\r\n return 1;\r\n }\r\n}).forEach((element) => {\r\n // When appending an element that is already a child, it will not\r\n // be duplicated, but removed from the old position first and then\r\n // added to the new one, which is exactly what we want:\r\n \r\n categoryList.appendChild(element);\r\n});Run Code Online (Sandbox Code Playgroud)\r\n#categoryList {\r\n font-family: monospace;\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n align-content: flex-start;\r\n list-style: none;\r\n padding: 0;\r\n margin: 0;\r\n width: 220px;\r\n border-right: 2px solid #000;\r\n}\r\n\r\n#categoryList > li {\r\n background: #000;\r\n color: #FFF;\r\n padding: 4px 8px;\r\n margin: 0 4px 4px 0;\r\n border-radius: 2px;\r\n}Run Code Online (Sandbox Code Playgroud)\r\n<ul id="categoryList">\r\n <li>Literature</li>\r\n <li>Science Fiction and Fantasy</li>\r\n <li>Harry Potter</li>\r\n <li>Movies and Films</li>\r\n <li>Books</li>\r\n</ul>Run Code Online (Sandbox Code Playgroud)\r\ninnerText对于等宽字体可能效果很好,但对于其他字体,您可以使用HTMLElement.offsetWidth它来考虑元素的实际宽度:
/**\r\n* Get the actual width of an element, taking into account margins \r\n* as well:\r\n*/\r\nfunction getElementWidth(element) {\r\n const style = window.getComputedStyle(element);\r\n \r\n // Assuming margins are in px:\r\n return element.offsetWidth + parseInt(style.marginLeft) + parseInt(style.marginRight);\r\n}\r\n\r\n\r\n// Sort the elements according to their actual width:\r\n\r\nconst categoryList = document.getElementById(\'categoryList\');\r\n\r\nArray.from(categoryList.children).sort((a, b) => {\r\n const aWidth = getElementWidth(a);\r\n const bWidth = getElementWidth(b);\r\n \r\n if (aWidth < bWidth) {\r\n return -1;\r\n } else if (aWidth === bWidth) {\r\n return 0;\r\n } else {\r\n return 1;\r\n }\r\n}).forEach((element) => {\r\n // When appending an element that is already a child, it will not\r\n // be duplicated, but removed from the old position first and then\r\n // added to the new one, which is exactly what we want:\r\n \r\n categoryList.appendChild(element);\r\n});Run Code Online (Sandbox Code Playgroud)\r\n#categoryList {\r\n font-family: monospace;\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n align-content: flex-start;\r\n list-style: none;\r\n padding: 0;\r\n margin: 0;\r\n width: 220px;\r\n border-right: 2px solid #000;\r\n}\r\n\r\n#categoryList > li {\r\n background: #000;\r\n color: #FFF;\r\n padding: 4px 8px;\r\n margin: 0 4px 4px 0;\r\n border-radius: 2px;\r\n}Run Code Online (Sandbox Code Playgroud)\r\n<ul id="categoryList">\r\n <li>Literature</li>\r\n <li>Science Fiction and Fantasy</li>\r\n <li>Harry Potter</li>\r\n <li>Movies and Films</li>\r\n <li>Books</li>\r\n</ul>Run Code Online (Sandbox Code Playgroud)\r\n您还可以实现自定义排序算法以不同的方式对它们进行排序。例如,您可能希望最小化每行上的空白空间:
\n\n/**\r\n* Get the actual width of an element, taking into account margins \r\n* as well:\r\n*/\r\nfunction getElementWidth(element) {\r\n const style = window.getComputedStyle(element);\r\n \r\n // Assuming margins are in px:\r\n return element.offsetWidth + parseInt(style.marginLeft) + parseInt(style.marginRight);\r\n}\r\n\r\n/**\r\n* Find the index of the widest element that fits in the available\r\n* space:\r\n*/\r\nfunction getBestFit(elements, availableSpace) {\r\n let minAvailableSpace = availableSpace;\r\n let bestFitIndex = -1;\r\n \r\n elements.forEach((element, i) => {\r\n if (element.used) {\r\n return;\r\n }\r\n \r\n const elementAvailableSpace = availableSpace - element.width;\r\n \r\n if (elementAvailableSpace >= 0 && elementAvailableSpace < minAvailableSpace) {\r\n minAvailableSpace = elementAvailableSpace;\r\n bestFitIndex = i;\r\n }\r\n });\r\n \r\n return bestFitIndex;\r\n}\r\n\r\n/**\r\n* Get the first element that hasn\'t been used yet.\r\n*/\r\nfunction getFirstNotUsed(elements) {\r\n for (let element of elements) {\r\n if (!element.used) {\r\n return element;\r\n }\r\n }\r\n}\r\n\r\n\r\n// Sort the elements according to their actual width:\r\n\r\nconst categoryList = document.getElementById(\'categoryList\');\r\nconst totalSpace = categoryList.clientWidth;\r\nconst items = Array.from(categoryList.children).map((element) => {\r\n return {\r\n element,\r\n used: false,\r\n width: getElementWidth(element),\r\n };\r\n});\r\nconst totalItems = items.length;\r\n\r\n// We want to keep the first element in the first position:\r\nconst firstItem = items[0];\r\nconst sortedElements = [firstItem.element];\r\n\r\nfirstItem.used = true;\r\n\r\n// We calculate the remaining space in the first row:\r\nlet availableSpace = totalSpace - firstItem.width;\r\n\r\n// We sort the other elements:\r\nfor (let i = 1; i < totalItems; ++i) {\r\n const bestFitIndex = getBestFit(items, availableSpace);\r\n \r\n let item;\r\n \r\n if (bestFitIndex === -1) {\r\n // If there\'s no best fit, we just take the first element\r\n // that hasn\'t been used yet to keep their order as close\r\n // as posible to the initial one:\r\n item = getFirstNotUsed(items);\r\n availableSpace = totalSpace - item.width;\r\n } else {\r\n item = items[bestFitIndex];\r\n availableSpace -= item.width;\r\n }\r\n \r\n sortedElements.push(item.element); \r\n item.used = true;\r\n}\r\n\r\nsortedElements.forEach((element) => {\r\n // When appending an element that is already a child, it will not\r\n // be duplicated, but removed from the old position first and then\r\n // added to the new one, which is exactly what we want:\r\n \r\n categoryList.appendChild(element);\r\n});Run Code Online (Sandbox Code Playgroud)\r\n#categoryList {\r\n font-family: monospace;\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n align-content: flex-start;\r\n list-style: none;\r\n padding: 0;\r\n margin: 0;\r\n width: 220px;\r\n border-right: 2px solid #000;\r\n}\r\n\r\n#categoryList > li {\r\n background: #000;\r\n color: #FFF;\r\n padding: 4px 8px;\r\n margin: 0 4px 4px 0;\r\n border-radius: 2px;\r\n}Run Code Online (Sandbox Code Playgroud)\r\n<ul id="categoryList">\r\n <li>Literature</li>\r\n <li>Science Fiction and Fantasy</li>\r\n <li>Harry Potter</li>\r\n <li>Movies and Films</li>\r\n <li>Books</li>\r\n</ul>Run Code Online (Sandbox Code Playgroud)\r\n最后,您可以flex: 1 0 auto在对列表中的每个子项进行排序后应用它们,以删除它们之间的任何不规则的空白:
/**\r\n* Get the actual width of an element, taking into account margins \r\n* as well:\r\n*/\r\nfunction getElementWidth(element) {\r\n const style = window.getComputedStyle(element);\r\n \r\n // Assuming margins are in px:\r\n return element.offsetWidth + parseInt(style.marginLeft) + parseInt(style.marginRight);\r\n}\r\n\r\n/**\r\n* Find the index of the widest element that fits in the available\r\n* space:\r\n*/\r\nfunction getBestFit(elements, availableSpace) {\r\n let minAvailableSpace = availableSpace;\r\n let bestFitIndex = -1;\r\n \r\n elements.forEach((element, i) => {\r\n if (element.used) {\r\n return;\r\n }\r\n \r\n const elementAvailableSpace = availableSpace - element.width;\r\n \r\n if (elementAvailableSpace >= 0 && elementAvailableSpace < minAvailableSpace) {\r\n minAvailableSpace = elementAvailableSpace;\r\n bestFitIndex = i;\r\n }\r\n });\r\n \r\n return bestFitIndex;\r\n}\r\n\r\n/**\r\n* Get the first element that hasn\'t been used yet.\r\n*/\r\nfunction getFirstNotUsed(elements) {\r\n for (let element of elements) {\r\n if (!element.used) {\r\n return element;\r\n }\r\n }\r\n}\r\n\r\n\r\n// Sort the elements according to their actual width:\r\n\r\nconst categoryList = document.getElementById(\'categoryList\');\r\nconst totalSpace = categoryList.clientWidth;\r\nconst items = Array.from(categoryList.children).map((element) => {\r\n return {\r\n element,\r\n used: false,\r\n width: getElementWidth(element),\r\n };\r\n});\r\nconst totalItems = items.length;\r\n\r\n// We want to keep the first element in the first position:\r\nconst firstItem = items[0];\r\nconst sortedElements = [firstItem.element];\r\n\r\nfirstItem.used = true;\r\n\r\n// We calculate the remaining space in the first row:\r\nlet availableSpace = totalSpace - firstItem.width;\r\n\r\n// We sort the other elements:\r\nfor (let i = 1; i < totalItems; ++i) {\r\n const bestFitIndex = getBestFit(items, availableSpace);\r\n \r\n let item;\r\n \r\n if (bestFitIndex === -1) {\r\n // If there\'s no best fit, we just take the first element\r\n // that hasn\'t been used yet to keep their order as close\r\n // as posible to the initial one:\r\n item = getFirstNotUsed(items);\r\n availableSpace = totalSpace - item.width;\r\n } else {\r\n item = items[bestFitIndex];\r\n availableSpace -= item.width;\r\n }\r\n \r\n sortedElements.push(item.element); \r\n item.used = true;\r\n}\r\n\r\nsortedElements.forEach((element) => {\r\n // When appending an element that is already a child, it will not\r\n // be duplicated, but removed from the old position first and then\r\n // added to the new one, which is exactly what we want:\r\n \r\n categoryList.appendChild(element);\r\n});\r\n\r\n// If you want to add a class to make the elements inside the list\r\n// expand, you have to do it after sorting them. Otherwise, they would\r\n// already take all available horizontal space and the sorting algorithm\r\n// won\'t do anything:\r\ncategoryList.classList.add(\'expand\');Run Code Online (Sandbox Code Playgroud)\r\n#categoryList {\r\n font-family: monospace;\r\n display: flex;\r\n flex-direction: row;\r\n flex-wrap: wrap;\r\n align-content: flex-start;\r\n list-style: none;\r\n padding: 0;\r\n margin: 0;\r\n width: 220px;\r\n border-right: 2px solid #000;\r\n}\r\n\r\n#categoryList > li {\r\n background: #000;\r\n color: #FFF;\r\n padding: 4px 8px;\r\n margin: 0 4px 4px 0;\r\n border-radius: 2px;\r\n}\r\n\r\n#categoryList.expand > li {\r\n flex: 1 1 auto;\r\n}Run Code Online (Sandbox Code Playgroud)\r\n<ul id="categoryList">\r\n <li>Literature</li>\r\n <li>Science Fiction and Fantasy</li>\r\n <li>Harry Potter</li>\r\n <li>Movies and Films</li>\r\n <li>Books</li>\r\n</ul>Run Code Online (Sandbox Code Playgroud)\r\n| 归档时间: |
|
| 查看次数: |
1395 次 |
| 最近记录: |