简体   繁体   中英

Rearrange chain based on window width

Solved: I've got the @EdnilsonMaia answer and adapted it http://codepen.io/anon/pen/QNGroX

I have a layout where there are a chain of users like so:

Window
-------------------
O - O - O - O - O |
                | |
O - O - O - O - O |
|                 |
O - O - O         | 
------------------
O = user
- = chain (icon)

链布局

When the user resizes the the window the number of users per line decrease and the chain need to be rearranged increasing the number of lines and decreasing the number of users per line. I found it very similar to the sorting algorithms.

Note that when rearranged the last user of the first line go to the last position of the second line and the first user of the second line to the first position of the third line, it must respect the order they are connected while changing positions.

What I need is a direction on how to code the algorithm in JS. So far my code changes the position of the users but doesn't take into consideration the correct order and also the chain icons. It also doesn't work when resizing back to the original size.

This is my code, note that each line is a separated 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> 

Try my solution:

1) PHP/HTML

Generate html elements.

<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

Note: I use FontAwesome to generate the icon

    .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

Get the container width of the list and calculate the maximum items per line. Define a flag to inform the direction of the next item in the chain. Use the variable (i) to calculate margins for every item and the direction of the next item. Calculate the amount of lines to define top margin.

$(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...
});

});

4) Demo https://jsfiddle.net/jftqLf1d/1/

Well, you can try the following. First make your users absolutely positioned:

div {
  background: lightgreen;
  line-height: 30px;
  position: absolute;
  text-align: center;
  width: 30px;
}

Then use the following approach: instead of sorting the HTML elements would be way easier to just calculate a location of an element based on simple maths. See:

// 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();
};

See jsfiddle . This is absolutely not a final solution, but a direction to go. You can improve many things starting from getting width / height / margin from CSS automatically; getting the parent instead of body for calculations; etc.

Adding a chain icon should be very easy: just make it as an ::after pseudo-element in CSS instead of HTML (by that you avoid lots of repetitions) and for every element in the end of the line just rotate it down (by assigning a proper class).

I saw no great answsers yet.

No offense , i look forward to see the right answer :)

So i go with my CSS idea (bg + flex) and a bit of jQuery to start sort it out, but not usable as it is , it is only for the show and as it goes it cares only about the first 4 lines.

 // 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> 

Having been intrigued by the challenge, I offer the following approach which works with one or two provisos:

  1. All the elements to be 'chained' are present in a single parent element, and
  2. The chain 'links' are not in the HTML.

The following JavaScript has been written using ES2015, with no attempt whatsoever to shim it for browsers that don't implement the (current, as of the time of writing) features.

That said, given the following HTML:

<ul>
  <li class="user"></li>
  <li class="user"></li>
  <li class="user"></li>
  <!-- ...as many as
       you like...   -->
  <li class="user"></li>
</ul>

The following JavaScript seems to work:

// 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);

The following CSS is used also:

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> 

JS Fiddle demo .

Edited: to address the previously-identified problem regarding element-alignment; which was because using display: inline-block collapses the white-space between <li> elements to a single space, whereas appending elements to the DOM does not insert any white-space. Which, of course, affects the spacing between elements, and the alignment of those elements with the previous rows.

So, the following line:

firstInLastRow.parentNode.insertBefore( document.createTextNode(' '), firstInLastRow);

Fixes the problem, by inserting a white-space text-node after each cloned <li> element.

 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> 

JS Fiddle demo .

Or, we can style the li.user elements with float: left , instead of display: inline-block , to present them in-line; which disregards the white-space (but still respects the margin property of the elements.

 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> 

JS Fiddle demo .

References:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM