skip to Main Content

I am working on a video calling application using reactjs. LocalTile component contains video of local user and RemoteTiles is returning videos of all remote users. My desired layout is like for 3 users:

===video1=video2===
=======video3======

for 4 users:

===video1=video2===
===video3=video4===

for 5 users:

===video1=video2=video3===
=======video4=video5======



<div id='gallery-parent' style={{ display: 'flex', flex: '1 1', flexDirection: 'column', gap: '1rem', position: 'relative' }}>
       <div style={{ alignItems: 'stretch',  display: 'flex', flex: '1 1', gap: '1rem',justifyContent:'center'}}>
         <LocalTile />
         <RemoteTiles />
       </div>                    
</div>

its not working for me as all users are rendering in same column and shrinking videos.

3

Answers


  1. Try setting a min-width: in (vw or %) unit + flex-wrap: wrap;

    • min-width: in vw unit relative to screen width, in % to parent width

    • flex-wrap: wrap; to allow the remaining el to pass to the next line

    Like this:

    {
      min-width: 18%; 
      flex-wrap: wrap;
    
      aspect-ratio: 1; // bind the height to the width 
    }
    
    Login or Signup to reply.
  2. I’m assuming a maximum of 2 rows and a maximum of 5 children.

    This is possible by using a combination of nth-child and nth-last-child to target elements based on the number of children.

    This requires setting certain widths based on the number of elements but since CSS cannot actually count elements this is not scalable.

    .container {
      display: flex;
      flex-wrap: wrap;
      margin-bottom: .5em;
      border:2px solid blue;
      padding: .25em;
    }
    
    .item {
      display: grid;
      place-items:center;
      font-size: 1.25em;
      height: 40px;
      background: lightblue;
      outline:1px solid black;
    }
    
    .item {
      flex:1;
    }
    
    
    
    /* 1 & 2 of 3 */
    .item:first-child:nth-last-child(3),
    .item:nth-child(2):nth-last-child(2)
    {
      flex: 0 0 50%;
    }
    
    /* 1 - 4 of 4 */
    .item:nth-child(1):nth-last-child(4),
    .item:nth-child(2):nth-last-child(3),
    .item:nth-child(3):nth-last-child(2),
    .item:nth-child(4):nth-last-child(1)
    {
      flex: 0 0 50%;
    }
    /* 1-3 of 5 */
    .item:nth-child(1):nth-last-child(5),
    .item:nth-child(2):nth-last-child(4),
    .item:nth-child(3):nth-last-child(3)
    {
      flex: 0 0 33.333%;
    }
    /* 4 & 5 of 5 */
    .item:nth-child(4):nth-last-child(2),
    .item:nth-child(5):nth-last-child(1)
    {
      flex: 0 0 50%;
    }
    <div class="container">
      <div class="item">1</div>
      <div class="item">2</div>
      <div class="item">3</div>
    </div>
    <div class="container">
      <div class="item">1</div>
      <div class="item">2</div>
      <div class="item">3</div>
      <div class="item">4</div>
    
    </div>
    
    <div class="container">
      <div class="item">1</div>
      <div class="item">2</div>
      <div class="item">3</div>
      <div class="item">4</div>
      <div class="item">5</div>
    </div>

    Note: If more rows are required, it would be better to have layouts like

    2 + 1 for 3 users

    2 + 2 for 4 users

    2 + 2 + 1 for 5 users etc.

    That makes the CSS much simpler and very scalable.

    Login or Signup to reply.
  3. I’m not 100% sure how you’d implement this in your larger codebase, but this is how I’d do it in vanilla JS. After the list of callers is rendered, you can grab the one just past halfway and insert a non-shrinking full-width element before it which will force the wrap to happen.

    This little example utility allows n number of users in the input box and will split them as expected

    function updateHandler(){
      if(input.value === '') return;
      //else
      const n = parseInt(input.value),
        half = Math.ceil(n/2)
      output.innerHTML = Array.apply(null, Array(n)).map(() => `<div class="caller"></div>`).join('')
      
      const halfwayElem = output.querySelectorAll('.caller')[half],
        splitter = document.createElement('hr')
        
      splitter.className = 'callers-divider'
      
      output.insertBefore(splitter, halfwayElem)
    }
    
    updateHandler()
    input.oninput = updateHandler
    #output {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
     }
     .caller {
      flex: 1;
      border: 1px solid black;
      margin: 8px;
      aspect-ratio: 4/3;
     }
     .callers-divider {
      display: block;
      width: 100%;
      flex-shrink: 0;
    }
    <label>Number of users connected: <input id="input" value="5" /></label>
    <div id="output"></div>

    Alternatively, if you don’t want the second row to take up the full width
    and just center the two callers, it can be done with CSS instead of DOM manipulation. This uses the bitwise operator n ^ 1 to determine if n is odd and if so, bump it up to the next even number

    function updateHandler(){
      if(input.value === '') return;
      //else
      let n = parseInt(input.value)
        
      output.innerHTML = Array.apply(null, Array(n)).map(() => `<div class="caller"></div>`).join('')
      
      const shiftedN = n ^ 1
      if(n !== shiftedN){
        ++n
      }
      
      const callerWidth = (200/n) + '%'
      output.style.setProperty('--caller-width', callerWidth)
    }
    
    updateHandler()
    input.oninput = updateHandler
    #output {
      width: 100%;
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
     }
     .caller {
      border: 1px solid black;
      aspect-ratio: 4/3;
      box-sizing: border-box;
      width: var(--caller-width);
     }
    <label>Number of users connected: <input id="input" value="5" /></label>
    <div id="output"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search