解决:我已经得到了@EdnilsonMaia的答案并对其进行了调整 http://codepen.io/anon/pen/QNGroX
我有一个布局,其中有一连串的用户喜欢这样:
Window
-------------------
O - O - O - O - O |
| |
O - O - O - O - O |
| |
O - O - O |
------------------
O = user
- = chain (icon)
当用户调整窗口大小时,每行的用户数量减少,并且需要重新排列链,增加行数并减少每行的用户数。我发现它与排序算法非常相似。
请注意,当重新排列第一行的最后一个用户转到第二行的最后一个位置并且第二行的第一个用户转到第三行的第一个位置时,它必须遵守它们在更改时连接的顺序位置。
我需要的是如何在JS中编写算法的方向。到目前为止,我的代码更改了用户的位置,但没有考虑正确的顺序以及链图标。调整大小恢复原始大小时,它也不起作用。
这是我的代码,请注意每行是一个单独的UL:
function log(msg, debug) {
debug = typeof debug !== 'undefined' ? debug : true;
if (debug) {
console.log(msg);
}
}
$(document).ready(function() {
$(window).on('resize', function() {
rearrangeChain(true);
});
function rearrangeChain(debug) {
debug = typeof debug !== 'undefined' ? debug : false;
log('------------------------', debug);
var win = $(window);
// Percentage
// 1170 -------- 100%
// size -------- x
var totalWindowWidth = 1170;
var windowWidth = win.width();
var percentage = (windowWidth * 100) / totalWindowWidth;
log('Window:' + percentage + '%', debug);
log('Window width:' + win.width() + 'px', debug);
var slotSize = 146.25;
var imagesPerLine = Math.floor(windowWidth / slotSize);
log('Images per line: ' + imagesPerLine, debug);
$('ul.users-chain-home').each(function(k) {
//var element = $(this);
var usersNumber = 1;
$(this).find('.user-image').each(function() {
var element = $(this).parent();
//console.log('users number', usersNumber, '>', imagesPerLine);
if (usersNumber > imagesPerLine) {
var nextLine = $('ul.users-chain-home')[k + 1];
console.log('Next line ' + (k + 1), nextLine);
if (typeof nextLine != 'undefined') {
console.log('Next line append', element[0]);
nextLine.appendChild(element[0]);
}
}
usersNumber++;
});
log('Users per line chain ' + k + ': ' + usersNumber, debug);
});
}
rearrangeChain(true);
});
div#wrapper {
display: inline-block
}
ul.users-chain-home {
list-style-type: none;
margin: 0;
padding: 0
}
ul.users-chain-home li {
display: inline;
}
ul.users-chain-home li div.chain-icon {
vertical-align: middle;
display: table-cell;
width: 40px;
height: 96px;
text-align: center;
}
ul.users-chain-home li div.join-chain {
vertical-align: middle;
display: table-cell;
height: 96px;
text-align: center;
}
img.chain-icon-vertical {
margin: 5px 42px 5px 0;
}
img.chain-icon-vertical-left {
margin: 5px 0 5px 38px;
}
<div id="wrapper">
<div>
<ul class="users-chain-home">
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/3.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/3.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon show-for-large">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li class="show-for-large">
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon show-for-large">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li class="show-for-large">
<img src="img/users/2.jpg" class="user-image">
</li>
</ul>
</div>
<div class="text-right">
<img src="img/assets/chain-icon-vertical.gif" class="chain-icon-vertical">
</div>
<div class="text-right">
<ul class="users-chain-home">
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/3.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/3.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/3.jpg" class="user-image">
</li>
</ul>
</div>
<div>
<img src="img/assets/chain-icon-vertical.gif" class="chain-icon-vertical-left">
</div>
<div>
<ul class="users-chain-home">
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/3.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/1.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/2.jpg" class="user-image">
</li>
<li class="chain-icon">
<div class="chain-icon">
<img src="img/assets/chain-icon.gif">
</div>
</li>
<li>
<img src="img/users/3.jpg" class="user-image">
</li>
</ul>
</div>
</div>
答案 0 :(得分:2)
试试我的解决方案:
1)PHP / HTML
生成html元素。
<div id="content">
<ul class="user-chain">
<?php for($i = 1; $i<=50; $i++): ?>
<li class="user-item">
<div class="user-container">
<img src="https://cdn1.iconfinder.com/data/icons/user-pictures/100/male3-128.png" class="user-avatar">
</div>
</li>
<?php endfor; ?>
</ul>
</div>
2)CSS
注意:我使用FontAwesome生成图标
.user-chain { margin: 0; padding: 0 }
.user-item { margin: 15px; list-style: none; max-width: 100px; position: absolute; }
.user-container { position: relative }
.user-avatar { max-width: 100px; max-height: 100px; display: block }
.user-container.chain:before {
content: "\f0c1";
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
text-decoration: inherit;
/*--adjust as necessary--*/
color: #000;
font-size: 18px;
padding-right: 0.5em;
position: absolute;
}
.user-container.chain-ltr:before {
top: 50%;
left: -24px;
-ms-transform: rotate(-45deg); /* IE 9 */
-webkit-transform: rotate(-45deg); /* Chrome, Safari, Opera */
transform: rotate(-45deg);
margin-top: -10px;
}
.user-container.chain-rtl:before {
top: 50%;
right: -32px;
-ms-transform: rotate(-45deg); /* IE 9 */
-webkit-transform: rotate(-45deg); /* Chrome, Safari, Opera */
transform: rotate(-45deg);
margin-top: -10px;
}
.user-container.chain-ttd:before {
top: -20px;
right: 50%;
-ms-transform: rotate(45deg); /* IE 9 */
-webkit-transform: rotate(45deg); /* Chrome, Safari, Opera */
transform: rotate(45deg);
margin-right: -12px;
}
3)jQuery
获取列表的容器宽度并计算每行的最大项目数。定义一个标志以通知链中下一个项目的方向。使用变量(i)计算每个项目的边距和下一个项目的方向。计算用于定义上边距的线数。
$(document).ready(function() {
var w_container = $('#content').width();
var elm_h = 100;
var elm_w = 100;
var elm_m = 15;
var maxNodesInLine = Math.floor(w_container / (elm_w + (elm_m *2)));
var direction = 'ltr';
var line = 0;
var i = 0;
function ltr(elm){
console.log('function ltr');
direction = 'ltr';
if(i == 0){
elm.css({"margin-left":0});
} else{
elm.css({"margin-left":(elm_w+(elm_m*2))*i});
}
elm.css({"margin-top":(elm_h+(elm_m*2))*line});
i++;
}
function rtl(elm){
console.log('function rtl');
if(i == (maxNodesInLine)){
elm.css({"margin-left":elm_m*2});
} else{
elm.css({"margin-left":(elm_w+(elm_m*2))*(maxNodesInLine - (i+1))});
}
elm.css({"margin-top":(elm_h+(elm_m*2))*line});
i++;
}
function ttd(elm){
console.log('function ttd');
elm.css({"margin-top":(elm_h+(elm_m*2))*line});
if(direction == 'ltr'){
direction = 'rtl';
elm.css({"margin-left":(elm_w+(elm_m*2))*(i-1)});
}
else{
direction = 'ltr';
elm.css({"margin-left":0});
}
i=1;
}
$( ".user-item" ).each(function( index ) {
elm = $(this);
if(direction == 'ltr' && i < maxNodesInLine)
{
ltr(elm);
$(elm).not(':first-child').children('.user-container').addClass('chain chain-ltr');
}
else if(i == (maxNodesInLine)){
line++;
ttd(elm);
$(elm).children('.user-container').addClass('chain chain-ttd');
}
else {
rtl(elm);
$(elm).children('.user-container').addClass('chain chain-rtl');
}
});
$(window).on('resize', function() {
console.log('Window re-sized.');
// todo: funtcion to update when resize window...
});
});
答案 1 :(得分:1)
好吧,您可以尝试以下方法。首先让您的用户绝对定位:
div {
background: lightgreen;
line-height: 30px;
position: absolute;
text-align: center;
width: 30px;
}
然后使用以下方法:而不是排序HTML元素将更容易根据简单的数学计算元素的位置。参见:
// generate 100 divs
document.body.innerHTML = Array.apply(null, new Array(100)).map(function(e, index) {
return '<div>' + index + '</div>';
}).join('');
// function which recalculates the positions
function render() {
var lineLength, margin = 10, height = 30, width = 30;
lineLength = Math.floor(document.body.clientWidth / (margin + width));
Array.apply(null, document.querySelectorAll('div')).forEach(function(element, index) {
var line = Math.floor(index / lineLength),
indexInLine = index - line * lineLength;
if (line % 2) indexInLine = lineLength - 1 - indexInLine;
element.style.left = indexInLine * (width + margin) + 'px';
element.style.top = line * (height + margin) + 'px';
});
}
// initial rendering call
render();
// call rendering every time window is resized
window.onresize = function() {
render();
};
见jsfiddle。这绝对不是最终解决方案,而是一个方向。你可以从CSS自动获取宽度/高度/边距开始改进很多东西;得到父母而不是身体进行计算;等
添加链图标应该非常简单:只需将其作为CSS中的::after
伪元素而不是HTML(通过避免大量重复)以及行末的每个元素将其向下旋转(通过指定适当的类)。
答案 2 :(得分:1)
我还没有看到好的回答者。
没有冒犯,我期待看到正确答案:)
所以我选择了我的CSS想法(bg + flex)和一些jQuery来开始排序,但不能使用它,它只适用于节目,因为它只关心前4线。
// basic idea , not usable as it is , cares about 4 lines ,it updates CSS flex order.
var nbrLi = $('#chain li').size();
var liWidth = $('#chain li').outerWidth(true);
var ulWidth = $("#chain").innerWidth();
var perLine = Math.floor(ulWidth / liWidth);
// var lastVisualOne= find out left or right line direction then and add margin equals to number of li missing to fill up entire line to avoid justify effect
$("#chain li").each(function(i) {
this.style.order = i;
var nbr = i;
if ((i > perLine - 1) && (i < (perLine * 2))) {//* i guess at this point it would be netter to use array() than an each() function */
this.style.order = (perLine * 3) - nbr;
this.style.color = "red";
}
else if ((i > (perLine * 3) - 1) && (i < (perLine * 4))) {// array() will be more efficient for sure :)
this.style.order = (perLine * 7) - nbr;
this.style.color = "red";
}
else {
this.style.order = "i";
}
});
ul {
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
text-align: justify;
background: linear-gradient(to top, white, white) no-repeat bottom right, url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCABBAAkDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9/KKKKACiiigAoqD+1Lb/AJ+IP+/go/tS2/5+IP8Av4KAPzt/be8daxpPxZ8QXCXV1C9rM0FreQzKtzZX+NTktYVGzzNrraWoAWRVwWyknmZT69/4SG+/5+ZPzr5B/be8CaxrHxb8QW62t1K1zK81pZQwhrm+v8anHazKd/mYjW6tdpWNlwGJePy8P9ff8I9ff8+0n5U+gHrlFFFID//Z) repeat-y 30px 60px, url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAIBAQIBAQICAgICAgICAwUDAwMDAwYEBAMFBwYHBwcGBwcICQsJCAgKCAcHCg0KCgsMDAwMBwkODw0MDgsMDAz/2wBDAQICAgMDAwYDAwYMCAcIDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCABBAAkDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9/KKKKACiiigAoqD+1Lb/AJ+IP+/go/tS2/5+IP8Av4KAPzt/be8daxpPxZ8QXCXV1C9rM0FreQzKtzZX+NTktYVGzzNrraWoAWRVwWyknmZT69/4SG+/5+ZPzr5B/be8CaxrHxb8QW62t1K1zK81pZQwhrm+v8anHazKd/mYjW6tdpWNlwGJePy8P9ff8I9ff8+0n5U+gHrlFFFID//Z) repeat-y calc(100% - 60px) -40px white;
background-size: 100px 100px, 9px 65px, 9px 65px;
}
li {
font-size: 2rem;
margin: 0 32px 32px 0;
display: inline-block;
width: 60px;
height: 60px;
line-height:60px;
text-align:center;
font-weight:bold;
color:white;
text-shadow:0 0 2px black;
border: solid gray;
border-radius: 50%;
position: relative;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAMAAAAPdrEwAAAAY1BMVEX///8AAACoqKjLy8sWFhZzc3Pu7u6srKwaGhrc3NyFhYXIyMivr68/Pz/8/PzQ0NDj4+NkZGS9vb319fUjIyNSUlK1tbVJSUmbm5sxMTF9fX05OTkICAjW1taVlZWOjo5bW1vxHZ4ZAAABZklEQVRYhe3ZyZKDIBAGYDZRcUFR4xKJef+njI6acZzEC50qp6b/C5y+orBRQEIw/ys2bpjJPgDLoQ1EUNYXcDnL6ZKhgJWLbpVpH6egtO2fNL1LULqhm1hQOt/S5nM0bJGoLa1Bab2RE9hlU3jfNIMtPhJWq9zB1t4Y67WCUpEM4PI4JxFTg69hZyPNNOdca26M1lPXAo28iL0qmVPXS3tXEAVoc0FfpG2cX4D6+gqeohxt+VamtHGj1XuZUqdVKcsjWrnQ0ZFMExc6PqRLlxnxD+kgPDHd+7edKZgHNGp92Q+XD0A03z/NIFJII4000kj/HVq40GYSyvA3zabm6rI1S6fRNen+6yv4dK4Wbiexwka2IGwheb7SREYmhNjFr7T0njRUkEYaaaSRRhpppJE+G738nQngaDOLpVyuc1q4O+x0vnK/EVl/dRyvPH/aXZ1U/riflmrsMEB5jAzl2gH+vYY5dx52WBgiF/12YwAAAABJRU5ErkJggg==) center no-repeat lightgray;
;
background-size: contain;
counter-increment: nbrli;
}
li:before {
content: url(https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcQ0iwo85deSrLC-3AspbJVxT6NxMlfj5Q3bd3V7vGdwd60mlp0pwA);
position: absolute;
line-height: 70px;
right: 70px
}
li:after {
content: counter(nbrli);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="chain">
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
<li> </li>
答案 3 :(得分:1)
受到挑战的好奇,我提供了以下方法,可以使用一两个附带条件:
以下JavaScript是使用ES2015编写的,没有尝试为没有实现(当前,截至编写时)功能的浏览器提供它。
那就是说,给出以下HTML:
<ul>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<!-- ...as many as
you like... -->
<li class="user"></li>
</ul>
以下JavaScript似乎有效:
// opts: Object, containing
// customisations to alter
// the default settings:
function chaining(opts) {
// chainSelector: String,
// the CSS selector passed to
// document.querySelectorAll()
// to select the relevant
// elements.
// firstInRowClass: String,
// the class-name to add to the
// first element in each row to
// identify it as such.
// lastInRowClose: String,
// As above, but to identify the
// last element in each row.
// rowStartIndex: Number or String,
// identifying the starting row-
// number. It can be either a
// Number or a numeric String
// (1, 2, 3... or '1', '2', '3'...)
let settings = {
'chainSelector': '.user',
'firstInRowClass': 'first',
'lastInRowClass': 'last',
'rowStartIndex': 0
};
// if we have an opts Object passed we iterate over
// the array of keys of that Object, using
// Array.prototype.forEach() to update the same
// property of the settings Object in order for
// user-supplied values to override the defaults:
if (opts) {
Object.keys(opts).forEach(function(keyname) {
settings[keyname] = opts[keyname];
});
}
// to save typing we assign the settings Object to
// the 's' variable:
let s = settings,
// here we use Array.from() to convert the collection
// returned by document.querySelectorAll(), into an
// Array; here document.querySelectorAll() uses the
// selector held in the s.chainSelector property to
// select the relevant elements:
elements = Array.from( document.querySelectorAll( s.chainSelector ) ),
// here we initialise the variable to 0, we determine a
// new 'row' each time the offsetTop of the found-elements
// is greater than the currentOffsetTop (and when it
// increases we update the variable to the new value):
currentOffsetTop = 0,
// here we use parseInt(), along with its radix, to
// ensure the starting index is a valid number, if
// not we use 0; and we use that to count the rows
// starting at the given starting index:
rowCount = parseInt( s.rowStartIndex, 10 ) || 0,
// we initialise the rowClass to 'odd' in order that
// we can select both 'odd' and 'even' rows, should it
// be necessary:
rowClass = 'odd';
// here we iterate over each of the found-elements,
// again using Array.prototype.forEach(), in order
// to remove the empty 'padding' elements (inserted
// later, under some circumstances):
elements.forEach(function(elem){
// if the classList (an Array-like collection of
// each of the class-names of an element) contains
// the class-name of 'padding':
if (elem.classList.contains('padding')) {
// we move to the element's parentNode and
// remove that child:
elem.parentNode.removeChild(elem);
}
});
// again, using Array.prototype.forEach(), along with
// an Arrow function syntax; here elem is the current
// element of the array over which we're iterating; the
// contents of the '{...}' block are a collection of
// actions taken to strip out all the class-names that
// this function adds to elements in order to identify
// them:
elements.forEach(elem => {
// here we have to use regular expressions in order to
// remove the string-literal of 'row' followed by one,
// or more, numbers (\d), if the string is surrounded
// by word-boundaries (\b); if the string is found we
// replace it with an empty, zero-length, String:
elem.className = elem.className.replace( /\brow\d+\b/, '');
// in the following lines we remove specific, known (or
// identifiable) class-names:
elem.classList.remove( s.firstInRowClass );
elem.classList.remove( s.lastInRowClass );
elem.classList.remove( 'odd' );
elem.classList.remove( 'even' );
elem.classList.remove( 'lastRow' );
});
// again, using Array.prototype.forEach():
elements.forEach(function(elem){
// if the currentOffsetTop variable, initialised
// to 0, is less than the offsetTop of the current
// element:
if (currentOffsetTop < elem.offsetTop) {
// we update the currentOffsetTop variable to
// that of the current element:
currentOffsetTop = elem.offsetTop;
// and because of the difference between the
// currentOffsetTop variable and the offsetTop
// of the current element we can surmise we've
// started a new row. Therefore we add the
// class-name held in the 's.firstInRowClass'
// variable:
elem.classList.add( s.firstInRowClass )
// if the current element, the first in a new row,
// has a previousElementSibling then that previous
// sibling must be the last element in its row:
if (elem.previousElementSibling) {
// therefore we add the class-name to that
// element to identify it as such:
elem.previousElementSibling.classList.add( s.lastInRowClass );
// we increment the rowCount variable because we're in
// a new row:
rowCount++;
// here we check that the previousElementSibling contains
// the class-name of 'odd'; if it does we change the
// rowClass (initialised earlier to 'odd') to 'even'.
// If it does not contain the class-name of 'odd' we
// set the rowClass to be 'odd':
rowClass = elem.previousElementSibling.classList.contains( 'odd' ) ? 'even' : 'odd';
}
}
// here we add the row-number class, 'row0', 'row1', etc...
elem.classList.add( 'row' + rowCount )
// here we add the 'odd' or 'even' class-name:
elem.classList.add( rowClass );
});
// creating a CSS selector to select those elements in the
// last row (from the string '.row' and the current 'rowCount'
// variable:
let lastRowSelector = '.row' + rowCount,
// here we pass that selector to document.querySelectorAll()
// to select the elements of the last row, and then use
// Array.from() to convert that collection into an Array:
lastRow = Array.from( document.querySelectorAll( lastRowSelector ) );
// iterating over the elements of the last-row to add the
// 'lastRow' class-name:
lastRow.forEach( elem => elem.classList.add('lastRow'));
// finding the last element in the last-row:
let lastElement = elements[ elements.length - 1 ];
// because there's no new row following the last element
// the method I used to set the class of the last-in-row
// elements doesn't work; so here we explicitly set it:
lastElement.classList.add( s.lastInRowClass );
// rowCount is greater than the starting index (so there
// is more than one row), and the first-element of the
// last-row contains the class of 'even':
if ( rowCount > s.rowStartIndex && lastRow[0].classList.contains('even')) {
// we get the elements of the penultimate row, by forming a
// CSS selector of the string '.row' + rowCount - 1; so if
// the last-row has the class of 'row4' (when rowCount = 4),
// this would create the selector of '.row3'.
// This selector is used by document.querySelectorAll() to
// return a collection of elements, which is passed to
// Array.from() to create an Array from that collection:
let penultimateRow = Array.from( document.querySelectorAll( '.row' + ( rowCount - 1) ) ),
// this retrieves the difference in the number of elements
// in the penultimate row and the number in the last-row:
rowDelta = penultimateRow.length - lastRow.length,
// creating a reference to the first element in the last-row:
firstInLastRow = lastRow[0],
// initialising an empty variable for later use:
clone;
// while rowDelta is not zero (and then decremented for
// the next iteration of the while loop):
while (rowDelta--) {
// we clone the the firstInLastRow element:
clone = firstInLastRow.cloneNode();
// here we make a naive assumption that the only
// classes held in the element (before manipulation by
// this script) will be those used in the CSS selector
// passed to the function (or held in the defaults).
// Here we set the class-name property of the
// cloned element, to the selector we used to select
// the chaining elements after replacing the periods (\.)
// in that selector with spaces and trimming trailing and
// leading white-space (this part bugs me; I should have
// found a better means to set the class-names to their
// 'pre-interfered-with state.):
clone.className = s.chainSelector.replace(/\./g,' ').trim();
// we add the class-name of 'padding' (which we use at the
// beginning of the script to remove the padding elements):
clone.classList.add( 'padding' );
// here we move from the firstInLastRow node to its parent,
// and then insert the newly-created clone before the
// firstInLastRow node (this is to line up the chain
// hanging down from the previous row with the top of the
// last-element in the last-row):
firstInLastRow.parentNode.insertBefore( clone, firstInLastRow );
}
}
}
// calling the function:
chaining();
// binding the function to the resize event of the window; allowing the
// elements to be 're-chained':
window.addEventListener('resize', chaining);
还使用以下CSS:
ul,
li {
/* to remove default list-styling from the
<ul> and <li> elements: */
list-style-type: none;
}
li.user {
/* aesthetics, adjust to your own taste: */
width: 3em;
height: 3em;
line-height: 3em;
display: inline-block;
margin: 0 0 1em 1em;
border: 2px solid #000;
box-sizing: border-box;
border-radius: 50%;
/* to allow the pseudo-elements to be
positioned relative to the <li>
elements: */
position: relative;
}
/* this, and the following rule, are both
to demonstrate the 'successful' matching;
obviously style to your own taste: */
li.first {
border-color: red;
}
li.last {
border-color: limegreen;
}
/* setting the common styles for the
pseudo-elements, both the ::before
and ::after: */
li::before, li::after {
/* Obviously use whatever image you feel appropriate
to depict the 'chain' links: */
content: url(https://i.stack.imgur.com/lPrR5.png);
position: absolute;
width: 24px;
height: 24px;
/* hiding these pseudo-elements by default: */
display: none;
}
/* showing the chains 'after' the <li> elements
in the '.odd' rows: */
li.odd::after {
/* showing the ::after pseudo-elements: */
display: block;
/* this is a lot more hit-and-miss than I'd
like; fine-tune to your own desires: */
top: calc(50% - 20px);
left: 100%;
}
/* styling the 'drop-down' chain links: */
li.odd.last::after,
li.even.first::before {
/* rotating the pseudo-element through
90 degrees; to give a vertical chain
(the actual rotation depends on the
chosen image, though): */
transform: rotate(90deg);
/* rotating the pseudo-element through
its centre point: */
transform-origin: 50% 50%;
top: 100%;
left: calc(50% - 10px);
}
/* on the '.even' rows we use the ::before
pseudo-elements: */
li.even::before {
display: block;
top: calc(50% - 20px);
right: 100%;
}
/* hiding the drop-down chains on the
first, and last, elements in the
.lastRow (to prevent unnecessary
dangling chains): */
li.lastRow.first::before,
li.lastRow.last::after {
display: none;
}
/* styling the padding elements; obviously
you should probably hide them entirely
(using opacity: 0; or visibility: hidden)
but they're visible here to show that they
exist and the purpose they serve: */
li.user.padding {
opacity: 0.2;
}
function chaining(opts) {
let settings = {
'chainSelector': '.user',
'firstInRowClass': 'first',
'lastInRowClass': 'last',
'rowStartIndex': 0
};
if (opts) {
Object.keys(opts).forEach(function(keyname) {
settings[keyname] = opts[keyname];
});
}
let s = settings,
elements = Array.from(document.querySelectorAll(s.chainSelector)),
currentOffsetTop = 0,
rowCount = parseInt(s.rowStartIndex, 10) || 0,
rowClass = 'odd';
elements.forEach(function(elem) {
if (elem.classList.contains('padding')) {
elem.parentNode.removeChild(elem);
}
});
elements.forEach(elem => {
elem.className = elem.className.replace(/\brow\d+\b/, '');
elem.classList.remove(s.firstInRowClass);
elem.classList.remove(s.lastInRowClass);
elem.classList.remove('odd');
elem.classList.remove('even');
elem.classList.remove('lastRow');
});
elements.forEach(function(elem) {
if (currentOffsetTop < elem.offsetTop) {
currentOffsetTop = elem.offsetTop;
elem.classList.add(s.firstInRowClass)
if (elem.previousElementSibling) {
elem.previousElementSibling.classList.add(s.lastInRowClass);
rowCount++;
rowClass = elem.previousElementSibling.classList.contains('odd') ? 'even' : 'odd';
}
}
elem.classList.add('row' + rowCount)
elem.classList.add(rowClass);
});
let lastRowSelector = '.row' + rowCount,
lastRow = Array.from(document.querySelectorAll(lastRowSelector));
lastRow.forEach(elem => elem.classList.add('lastRow'));
let lastElement = elements[elements.length - 1];
lastElement.classList.add(s.lastInRowClass);
if (rowCount > s.rowStartIndex && lastRow[0].classList.contains('even')) {
let penultimateRow = Array.from(document.querySelectorAll('.row' + (rowCount - 1))),
rowDelta = penultimateRow.length - lastRow.length,
firstInLastRow = lastRow[0],
clone;
while (rowDelta--) {
clone = firstInLastRow.cloneNode();
clone.className = s.chainSelector.replace(/\./g, ' ').trim();
clone.classList.add('padding');
firstInLastRow.parentNode.insertBefore(clone, firstInLastRow);
}
}
}
chaining();
window.addEventListener('resize', chaining);
ul,
li {
list-style-type: none;
}
li.user {
width: 3em;
height: 3em;
line-height: 3em;
position: relative;
display: inline-block;
margin: 0 0 1em 1em;
border: 2px solid #000;
box-sizing: border-box;
border-radius: 50%;
}
li.first {
border-color: red;
}
li.last {
border-color: limegreen;
}
li::before,
li::after {
content: url(https://i.stack.imgur.com/lPrR5.png);
position: absolute;
width: 24px;
height: 24px;
display: none;
}
li.odd::after {
display: block;
top: calc(50% - 20px);
left: 100%;
}
li.odd.last::after,
li.even.first::before {
transform: rotate(90deg);
transform-origin: 50% 50%;
top: 100%;
left: calc(50% - 10px);
}
li.even::before {
display: block;
top: calc(50% - 20px);
right: 100%;
}
li.lastRow.first::before,
li.lastRow.last::after {
display: none;
}
li.user.padding {
opacity: 0.2;
}
<ul>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
</ul>
已编辑:,以解决先前发现的有关元素对齐的问题;这是因为使用display: inline-block
将<li>
元素之间的空格折叠到单个空格,而向DOM添加元素不会插入任何空格。当然,这会影响元素之间的间距,以及这些元素与前一行的对齐。
所以,以下一行:
firstInLastRow.parentNode.insertBefore( document.createTextNode(' '), firstInLastRow);
通过在每个克隆的<li>
元素之后插入空白文本节点来解决问题。
function chaining(opts) {
let settings = {
'chainSelector': '.user',
'firstInRowClass': 'first',
'lastInRowClass': 'last',
'rowStartIndex': 0
};
if (opts) {
Object.keys(opts).forEach(function(keyname) {
settings[keyname] = opts[keyname];
});
}
let s = settings,
elements = Array.from(document.querySelectorAll(s.chainSelector)),
currentOffsetTop = 0,
rowCount = parseInt(s.rowStartIndex, 10) || 0,
rowClass = 'odd';
elements.forEach(function(elem) {
if (elem.classList.contains('padding')) {
elem.parentNode.removeChild(elem);
}
});
elements.forEach(elem => {
elem.className = elem.className.replace(/\brow\d+\b/, '');
elem.classList.remove(s.firstInRowClass);
elem.classList.remove(s.lastInRowClass);
elem.classList.remove('odd');
elem.classList.remove('even');
elem.classList.remove('lastRow');
});
elements.forEach(function(elem) {
if (currentOffsetTop < elem.offsetTop) {
currentOffsetTop = elem.offsetTop;
elem.classList.add(s.firstInRowClass)
if (elem.previousElementSibling) {
elem.previousElementSibling.classList.add(s.lastInRowClass);
rowCount++;
rowClass = elem.previousElementSibling.classList.contains('odd') ? 'even' : 'odd';
}
}
elem.classList.add('row' + rowCount)
elem.classList.add(rowClass);
});
let lastRowSelector = '.row' + rowCount,
lastRow = Array.from(document.querySelectorAll(lastRowSelector));
lastRow.forEach(elem => elem.classList.add('lastRow'));
let lastElement = elements[elements.length - 1];
lastElement.classList.add(s.lastInRowClass);
if (rowCount > s.rowStartIndex && lastRow[0].classList.contains('even')) {
let penultimateRow = Array.from(document.querySelectorAll('.row' + (rowCount - 1))),
rowDelta = penultimateRow.length - lastRow.length,
firstInLastRow = lastRow[0],
clone;
while (rowDelta--) {
clone = firstInLastRow.cloneNode();
clone.className = s.chainSelector.replace(/\./g, ' ').trim();
clone.classList.add('padding');
firstInLastRow.parentNode.insertBefore(clone, firstInLastRow);
// new line added here to insert the one-space textNode:
firstInLastRow.parentNode.insertBefore( document.createTextNode(' '), firstInLastRow);
}
}
}
chaining();
window.addEventListener('resize', chaining);
ul,
li {
list-style-type: none;
}
li.user {
width: 3em;
height: 3em;
line-height: 3em;
position: relative;
display: inline-block;
margin: 0 0 1em 1em;
border: 2px solid #000;
box-sizing: border-box;
border-radius: 50%;
}
li.first {
border-color: red;
}
li.last {
border-color: limegreen;
}
li::before,
li::after {
content: url(https://i.stack.imgur.com/lPrR5.png);
position: absolute;
width: 24px;
height: 24px;
display: none;
}
li.odd::after {
display: block;
top: calc(50% - 20px);
left: 100%;
}
li.odd.last::after,
li.even.first::before {
transform: rotate(90deg);
transform-origin: 50% 50%;
top: 100%;
left: calc(50% - 10px);
}
li.even::before {
display: block;
top: calc(50% - 20px);
right: 100%;
}
li.lastRow.first::before,
li.lastRow.last::after {
display: none;
}
li.user.padding {
opacity: 0.2;
}
<ul>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
</ul>
或者,我们可以使用li.user
而不是float: left
为display: inline-block
元素设置样式,以便在线呈现它们;它忽略了空白区域(但仍然尊重元素的margin
属性。
function chaining(opts) {
let settings = {
'chainSelector': '.user',
'firstInRowClass': 'first',
'lastInRowClass': 'last',
'rowStartIndex': 0
};
if (opts) {
Object.keys(opts).forEach(function(keyname) {
settings[keyname] = opts[keyname];
});
}
let s = settings,
elements = Array.from(document.querySelectorAll(s.chainSelector)),
currentOffsetTop = 0,
rowCount = parseInt(s.rowStartIndex, 10) || 0,
rowClass = 'odd';
elements.forEach(function(elem) {
if (elem.classList.contains('padding')) {
elem.parentNode.removeChild(elem);
}
});
elements.forEach(elem => {
elem.className = elem.className.replace(/\brow\d+\b/, '');
elem.classList.remove(s.firstInRowClass);
elem.classList.remove(s.lastInRowClass);
elem.classList.remove('odd');
elem.classList.remove('even');
elem.classList.remove('lastRow');
});
elements.forEach(function(elem) {
if (currentOffsetTop < elem.offsetTop) {
currentOffsetTop = elem.offsetTop;
elem.classList.add(s.firstInRowClass)
if (elem.previousElementSibling) {
elem.previousElementSibling.classList.add(s.lastInRowClass);
rowCount++;
rowClass = elem.previousElementSibling.classList.contains('odd') ? 'even' : 'odd';
}
}
elem.classList.add('row' + rowCount)
elem.classList.add(rowClass);
});
let lastRowSelector = '.row' + rowCount,
lastRow = Array.from(document.querySelectorAll(lastRowSelector));
lastRow.forEach(elem => elem.classList.add('lastRow'));
let lastElement = elements[elements.length - 1];
lastElement.classList.add(s.lastInRowClass);
if (rowCount > s.rowStartIndex && lastRow[0].classList.contains('even')) {
let penultimateRow = Array.from(document.querySelectorAll('.row' + (rowCount - 1))),
rowDelta = penultimateRow.length - lastRow.length,
firstInLastRow = lastRow[0],
clone;
while (rowDelta--) {
clone = firstInLastRow.cloneNode();
clone.className = s.chainSelector.replace(/\./g, ' ').trim();
clone.classList.add('padding');
firstInLastRow.parentNode.insertBefore(clone, firstInLastRow);
}
}
}
chaining();
window.addEventListener('resize', chaining);
ul,
li {
list-style-type: none;
}
li.user {
width: 3em;
height: 3em;
line-height: 3em;
position: relative;
float: left;
margin: 0 0 1em 1em;
border: 2px solid #000;
box-sizing: border-box;
border-radius: 50%;
}
li.first {
border-color: red;
}
li.last {
border-color: limegreen;
}
li::before,
li::after {
content: url(https://i.stack.imgur.com/lPrR5.png);
position: absolute;
width: 24px;
height: 24px;
display: none;
}
li.odd::after {
display: block;
top: calc(50% - 20px);
left: 100%;
}
li.odd.last::after,
li.even.first::before {
transform: rotate(90deg);
transform-origin: 50% 50%;
top: 100%;
left: calc(50% - 10px);
}
li.even::before {
display: block;
top: calc(50% - 20px);
right: 100%;
}
li.lastRow.first::before,
li.lastRow.last::after {
display: none;
}
li.user.padding {
opacity: 0.2;
}
<ul>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
<li class="user"></li>
</ul>
参考文献:
Array.prototype.forEach()
。Array.from()
。document.createTextNode()
。document.querySelectorAll()
。Element.classList
API。EventTarget.addEventListener()
。Node.cloneNode()
。Node.insertBefore()
。Node.parentNode()
。Node.removeChild()
。Object.keys()
。String.prototype.replace()
。String.prototype.trim()
。