Я работаю над приложением для видеовызовов, используя реакцию. Компонент LocalTile содержит видео локального пользователя, а RemoteTiles возвращает видео всех удаленных пользователей. Мой желаемый макет похож на 3 пользователей:
===video1=video2===
=======video3======
для 4 пользователей:
===video1=video2===
===video3=video4===
для 5 пользователей:
===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>
У меня это не работает, так как все пользователи визуализируют в одном столбце и сжимают видео.
🤔 А знаете ли вы, что...
JavaScript поддерживает работу с графикой и аудио, что позволяет создавать мультимедийные веб-приложения.
Попробуйте установить min-width: in (vw or %) unit + flex-wrap: wrap;
минимальная ширина: в единицах vw относительно ширины экрана, в % от родительской ширины
flex-wrap: обертка; чтобы позволить оставшемуся элю перейти на следующую строку
Так:
{
min-width: 18%;
flex-wrap: wrap;
aspect-ratio: 1; // bind the height to the width
}
Я предполагаю максимум 2 строки и максимум 5 детей.
Это возможно, если использовать комбинацию nth-child
и nth-last-child
для выбора элементов в зависимости от количества дочерних элементов.
Для этого необходимо установить определенную ширину в зависимости от количества элементов, но поскольку CSS не может фактически подсчитывать элементы, это невозможно масштабировать.
.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>
Примечание. Если требуется больше строк, лучше использовать такие макеты, как
2+1 для 3 пользователей
2+2 для 4 пользователей
2+2+1 для 5 пользователей и т.д.
Это делает CSS намного проще и масштабируемее.
Я не уверен на 100%, как бы вы реализовали это в своей более крупной кодовой базе, но я бы сделал это именно так в ванильном JS. После того, как список вызывающих абонентов будет отображен, вы можете взять тот, который находится чуть дальше половины, и вставить перед ним несжимающийся элемент полной ширины, что приведет к выполнению переноса.
Этот небольшой пример утилиты допускает в поле ввода n количество пользователей и разделяет их, как и ожидалось.
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>
В качестве альтернативы, если вы не хотите, чтобы вторая строка занимала всю ширину
и просто центрируйте два вызывающих объекта, это можно сделать с помощью CSS вместо манипуляций с DOM. При этом используется побитовый оператор n ^ 1
, чтобы определить, является ли n
нечетным, и если да, то увеличить его до следующего четного числа.
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>
Хотя это не очень хорошо, но вы можете сделать это с помощью CSS Grid. Например, что-то вроде этого:
body {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 24px;
}
.videos {
display: inline-grid;
gap: 4px;
counter-reset: counter;
&:has(.video:nth-last-child(3)) {
grid-template-columns: 1fr 1fr;
.video:last-child {
grid-column: span 2;
margin: 0 auto;
}
}
&:has(.video:nth-last-child(4)) {
grid-template-columns: 1fr 1fr;
.video:last-child {
grid-column: unset;
}
}
&:has(.video:nth-last-child(5)) {
grid-template-columns: repeat(6, 1fr);
.video {
grid-column: span 2;
&:last-child {
grid-column: span 3;
margin: 0 auto 0 0;
}
&:nth-last-child(2) {
grid-column: span 3;
margin: 0 0 0 auto;
}
}
}
}
.video {
background: orange;
&:before {
content: 'video' counter(counter);
counter-increment: counter;
}
}
<div class = "videos">
<div class = "video"></div>
<div class = "video"></div>
<div class = "video"></div>
</div>
<div class = "videos">
<div class = "video"></div>
<div class = "video"></div>
<div class = "video"></div>
<div class = "video"></div>
</div>
<div class = "videos">
<div class = "video"></div>
<div class = "video"></div>
<div class = "video"></div>
<div class = "video"></div>
<div class = "video"></div>
</div>