У меня есть боковое меню, в котором есть индикатор, показывающий выбранную кнопку.
Результат, которого я ожидал, состоял в том, что индикатор двигался, а элементы li — нет. Но оказывается, они оба двигаются. Я знаю, что то, как я закодировал это, очень не нужно, но я не знаю, как это сделать иначе. Может ли кто-нибудь объяснить мне, почему элементы li перемещаются, как я могу сделать свой код более эффективным и как решить мою проблему?
const menuButtonElements = document.querySelectorAll('#menu-sdbr-list-item button');
const menuIndicatorElement = document.getElementById('menu-sdbr-list-indicator');
const menuSidebarUlElement = document.getElementById('menu-sdbr-list');
let selectedButtonIndex = 0;
menuButtonElements.forEach((value, index) => {
value.addEventListener('click', () => {
selectedButtonIndex = index;
menuIndicatorElement.setAttribute('style', `
margin-bottom: ${(index + 1) * -65}px;
`)
menuButtonElements.forEach(value => {
value.setAttribute('style', `
color: var(--color-surface-300);
`)
})
menuButtonElements[index].setAttribute('style', `
color: white;
`)
})
})@import url('https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@import "https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Round";
body {
--color-primary-100: rgb(105, 54, 245);
--color-primary-200: rgb(128, 78, 247);
--color-primary-300: rgb(148, 100, 249);
--color-primary-400: rgb(166, 122, 251);
--color-primary-500: rgb(183, 144, 252);
--color-primary-600: rgb(199, 165, 253);
--color-surface-100: rgb(0, 0, 0);
--color-surface-200: rgb(30, 30, 30);
--color-surface-300: rgb(53, 53, 53);
--color-surface-400: rgb(78, 78, 78);
--color-surface-500: rgb(105, 105, 105);
--color-surface-600: rgb(133, 133, 133);
--color-surface-mixed-100: rgb(26, 22, 37);
--color-surface-mixed-200: rgb(40, 35, 48);
--color-surface-mixed-300: rgb(63, 58, 70);
--color-surface-mixed-400: rgb(88, 83, 94);
--color-surface-mixed-500: rgb(113, 109, 119);
--color-surface-mixed-600: rgb(140, 136, 144);
--color-primary-100-mix: 105, 54, 245;
--color-primary-500-mix: 183, 144, 252;
font-family: Fira Sans Condensed, Arial;
margin: 0;
}
.menu-sidebar-overlay {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
z-index: 200;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
color: white;
}
.menu-sidebar {
position: relative;
height: 100%;
width: 35vw;
background-color: var(--color-primary-100);
display: flex;
}
.menu-sidebar .rectangle {
width: 15%;
height: 100%;
background-color: var(--color-surface-300);
}
.menu-sdbr-title {
display: inline;
color: var(--color-primary-200);
font-size: 40px;
margin: 15px 0 0 10px;
color: white;
}
.menu-sdbr-contents {
width: 100%;
position: relative;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
margin-left: 10px;
}
.menu-sdbr-list {
position: absolute;
width: 100%;
padding: 0;
margin: 90px 0 0 0;
list-style-type: none;
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
flex: 1;
height: 100%;
}
.menu-sdbr-list-indicator {
margin-bottom: -65px;
width: 100%;
height: 60px;
background-color: var(--color-surface-300);
z-index: 250;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
transition: margin-bottom 150ms;
}
.menu-sdbr-list-item button {
position: relative;
background-color: transparent;
border: 0 solid rgba(0, 0, 0, 1);
width: 100%;
padding: 0 15px;
height: 60px;
text-align: left;
display: flex;
align-items: center;
color: var(--color-surface-300);
z-index: 300;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
font-size: 15px;
font-weight: 400;
z-index: 300;
/*
border: 1px solid rgba(0, 0, 0, 1);
border-right: none;
*/
transition: color 150ms;
}
.menu-sdbr-list-item button .material-icons {
margin-right: 15px;
font-size: 20px;
}<nav class = "menu-sidebar-overlay">
<div class = "menu-sidebar">
<div class = "menu-sdbr-contents">
<h1 class = "menu-sdbr-title">MENU</h1>
<ul class = "menu-sdbr-list" id = "menu-sdbr-list">
<li class = "menu-sdbr-list-indicator" id = "menu-sdbr-list-indicator">
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">dark_mode</span>
Dark Mode
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">settings</span>
Settings
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">logout</span>
Log Out
</button>
</li>
</ul>
</div>
<div class = "rectangle"></div>
</div>
</nav>@Pointy О, я этого не знал, я обновлю это очень быстро
Чтобы быть более ясным, причина, по которой хорошо сохранять «честность» со структурой HTML, заключается в том, что когда вы этого не делаете, вам приходится иметь дело с тем, как браузер решает исправить ситуацию за вас, что иногда может привести к странным вещам. В этом случае браузеры, кажется, терпят плохие структуры <ul>, по крайней мере, в разумных пределах, но я бы все равно это исправил, если бы заметил это в коде, который мне небезразличен.
@Pointy о, хорошо, теперь я понял, спасибо
мой плохой, это <menu> -> <menu> может заменить верхний <ul>
Отрицательное значение margin-bottom, которое вы устанавливаете для этого элемента индикатора, позволяет следующим элементам «скользить вверх». Удалите начальное отрицательное поле из таблицы стилей, замените его на position: relative. А в вашей JS части ставьте не margin-bottom, а вместо этого top - но с положительным коэффициентом (т.е. умножайте на 65, а не -65)
И в идеале этот индикатор должен быть реализован не через пустой дополнительный элемент списка (семантически тоже довольно плохой), а через псевдо-потомок (::before) на UL.
На мой взгляд, это не лучшее решение, которое вы можете найти. У вас есть смещение контейнера, потому что вы играете с полями и эффективно меняете высоту контейнера (который центрируется на родительском элементе). Вместо того, чтобы перемещать «ли», я бы поместил черную вкладку над линиями (в абсолютном выражении) и переместил ее.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вместо того, чтобы перемещать абсолютно позиционированный элемент вверх и вниз, было бы проще добавить и удалить класс родительского элемента li, а затем добавить к нему фон и выделить кнопку на основе класса.
Это также имеет дополнительное преимущество, позволяя вам добавить этот класс в один из li, если вы хотите, чтобы он был предварительно выбран, в приведенном ниже примере я добавил его в первый li.
const menuButtonElements = document.querySelectorAll('#menu-sdbr-list-item button');
// change the name of this foreach variable so you don't have matching var name inside the closure
menuButtonElements.forEach(button => {
button.addEventListener('click', () => {
// remove class from all button parent lis
menuButtonElements.forEach(value => {
value.parentNode.classList.remove('selected');
});
// add class to the clicked button parent li
button.parentNode.classList.add('selected');
})
})@import url('https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@import "https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Round";
body {
--color-primary-100: rgb(105, 54, 245);
--color-primary-200: rgb(128, 78, 247);
--color-primary-300: rgb(148, 100, 249);
--color-primary-400: rgb(166, 122, 251);
--color-primary-500: rgb(183, 144, 252);
--color-primary-600: rgb(199, 165, 253);
--color-surface-100: rgb(0, 0, 0);
--color-surface-200: rgb(30, 30, 30);
--color-surface-300: rgb(53, 53, 53);
--color-surface-400: rgb(78, 78, 78);
--color-surface-500: rgb(105, 105, 105);
--color-surface-600: rgb(133, 133, 133);
--color-surface-mixed-100: rgb(26, 22, 37);
--color-surface-mixed-200: rgb(40, 35, 48);
--color-surface-mixed-300: rgb(63, 58, 70);
--color-surface-mixed-400: rgb(88, 83, 94);
--color-surface-mixed-500: rgb(113, 109, 119);
--color-surface-mixed-600: rgb(140, 136, 144);
--color-primary-100-mix: 105, 54, 245;
--color-primary-500-mix: 183, 144, 252;
font-family: Fira Sans Condensed, Arial;
margin: 0;
}
.menu-sidebar-overlay {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
z-index: 200;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
color: white;
}
.menu-sidebar {
position: relative;
height: 100%;
width: 35vw;
background-color: var(--color-primary-100);
display: flex;
}
.menu-sidebar .rectangle {
width: 15%;
height: 100%;
background-color: var(--color-surface-300);
}
.menu-sdbr-title {
display: inline;
color: var(--color-primary-200);
font-size: 40px;
margin: 15px 0 0 10px;
color: white;
}
.menu-sdbr-contents {
width: 100%;
position: relative;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
margin-left: 10px;
}
.menu-sdbr-list {
position: absolute;
width: 100%;
padding: 0;
margin: 90px 0 0 0;
list-style-type: none;
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
flex: 1;
height: 100%;
}
.menu-sdbr-list-item button {
position: relative;
background-color: transparent;
border: 0 solid rgba(0, 0, 0, 1);
width: 100%;
padding: 0 15px;
height: 60px;
text-align: left;
display: flex;
align-items: center;
color: var(--color-surface-300);
z-index: 300;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
font-size: 15px;
font-weight: 400;
z-index: 300;
/*
border: 1px solid rgba(0, 0, 0, 1);
border-right: none;
*/
transition: color 150ms;
}
.menu-sdbr-list-item button .material-icons {
margin-right: 15px;
font-size: 20px;
}
/* the below are the new classes for the selected item */
.menu-sdbr-list-item.selected {
background-color: var(--color-surface-300);
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}
.menu-sdbr-list-item.selected button {
color: white;
}<nav class = "menu-sidebar-overlay">
<div class = "menu-sidebar">
<div class = "menu-sdbr-contents">
<h1 class = "menu-sdbr-title">MENU</h1>
<ul class = "menu-sdbr-list" id = "menu-sdbr-list">
<li class = "menu-sdbr-list-item selected" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">dark_mode</span>
Dark Mode
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">settings</span>
Settings
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">logout</span>
Log Out
</button>
</li>
</ul>
</div>
<div class = "rectangle"></div>
</div>
</nav>Извините, только что понял, что у вас есть анимация на вашем фоне, чтобы он скользил между щелкнутыми элементами - приведенный выше ответ не будет этого делать, вместо этого вам нужно будет использовать абсолютное позиционирование для перемещения выделения без перемещения остальной его части:
const menuButtonElements = document.querySelectorAll('#menu-sdbr-list-item button');
const menuIndicatorElement = document.getElementById('menu-sdbr-list-indicator');
const menuSidebarUlElement = document.getElementById('menu-sdbr-list');
let selectedButtonIndex = 0;
const elementHeight = 65;
const initialPosition = -((menuButtonElements.length - 1) / 2) * elementHeight;
menuButtonElements.forEach((value, index) => {
value.addEventListener('click', () => {
selectedButtonIndex = index;
menuIndicatorElement.setAttribute('style', `
margin-top: ${initialPosition + (index * elementHeight)}px;
`)
menuButtonElements.forEach(value => {
value.setAttribute('style', `
color: var(--color-surface-300);
`)
})
menuButtonElements[index].setAttribute('style', `
color: white;
`)
})
})@import url('https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@import "https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Round";
body {
--color-primary-100: rgb(105, 54, 245);
--color-primary-200: rgb(128, 78, 247);
--color-primary-300: rgb(148, 100, 249);
--color-primary-400: rgb(166, 122, 251);
--color-primary-500: rgb(183, 144, 252);
--color-primary-600: rgb(199, 165, 253);
--color-surface-100: rgb(0, 0, 0);
--color-surface-200: rgb(30, 30, 30);
--color-surface-300: rgb(53, 53, 53);
--color-surface-400: rgb(78, 78, 78);
--color-surface-500: rgb(105, 105, 105);
--color-surface-600: rgb(133, 133, 133);
--color-surface-mixed-100: rgb(26, 22, 37);
--color-surface-mixed-200: rgb(40, 35, 48);
--color-surface-mixed-300: rgb(63, 58, 70);
--color-surface-mixed-400: rgb(88, 83, 94);
--color-surface-mixed-500: rgb(113, 109, 119);
--color-surface-mixed-600: rgb(140, 136, 144);
--color-primary-100-mix: 105, 54, 245;
--color-primary-500-mix: 183, 144, 252;
font-family: Fira Sans Condensed, Arial;
margin: 0;
}
.menu-sidebar-overlay {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
z-index: 200;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
color: white;
}
.menu-sidebar {
position: relative;
height: 100%;
width: 35vw;
background-color: var(--color-primary-100);
display: flex;
}
.menu-sidebar .rectangle {
width: 15%;
height: 100%;
background-color: var(--color-surface-300);
}
.menu-sdbr-title {
display: inline;
color: var(--color-primary-200);
font-size: 40px;
margin: 15px 0 0 10px;
color: white;
}
.menu-sdbr-contents {
width: 100%;
position: relative;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
margin-left: 10px;
}
.menu-sdbr-list {
position: absolute;
width: 100%;
padding: 0;
margin: 90px 0 0 0;
list-style-type: none;
display: flex;
flex-direction: column;
justify-content: center;
gap: 5px;
flex: 1;
height: 100%;
}
.menu-sdbr-list-indicator {
position: absolute;
top: 50%;
margin-top: -65px;
right: 0;
width: 100%;
height: 60px;
background-color: var(--color-surface-300);
z-index: 250;
transform: translateY(-50%);
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
transition: margin-top 150ms;
}
.menu-sdbr-list-item button {
position: relative;
background-color: transparent;
border: 0 solid rgba(0, 0, 0, 1);
width: 100%;
padding: 0 15px;
height: 60px;
text-align: left;
display: flex;
align-items: center;
color: var(--color-surface-300);
z-index: 300;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
font-size: 15px;
font-weight: 400;
z-index: 300;
/*
border: 1px solid rgba(0, 0, 0, 1);
border-right: none;
*/
transition: color 150ms;
}
.menu-sdbr-list-item button .material-icons {
margin-right: 15px;
font-size: 20px;
}<nav class = "menu-sidebar-overlay">
<div class = "menu-sidebar">
<div class = "menu-sdbr-contents">
<h1 class = "menu-sdbr-title">MENU</h1>
<ul class = "menu-sdbr-list" id = "menu-sdbr-list">
<li class = "menu-sdbr-list-indicator" id = "menu-sdbr-list-indicator">
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button style = "color: white">
<span class = "material-icons">dark_mode</span>
Dark Mode
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">settings</span>
Settings
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">logout</span>
Log Out
</button>
</li>
</ul>
</div>
<div class = "rectangle"></div>
</div>
</nav>Начальный margin-top основан на количестве элементов над средним элементом, который есть — поскольку есть три элемента, и каждый элемент имеет высоту 60 пикселей с промежутком 5 пикселей, есть один элемент выше, поэтому вы имеете отступ -65 пикселей. Если бы было 4 элемента, было бы 1,5 элемента выше, то есть 1,5 * -65.
Если вы измените -65 в css, вам также нужно будет изменить его в js.
Ух ты. Я понимаю часть css, но я не понимаю переменную initialPosition, если все в порядке, можете ли вы объяснить мне это?
Немного хакерский, но это та функциональность, которую вы ищете?
const menuButtonElements = document.querySelectorAll('#menu-sdbr-list-item button');
const menuIndicatorElement = document.getElementById('menu-sdbr-list-indicator');
const menuSidebarUlElement = document.getElementById('menu-sdbr-list');
let selectedButtonIndex = 0;
let marginTop = 27;
menuButtonElements.forEach((value, index) => {
value.addEventListener('click', () => {
menuIndicatorElement.style.display = "block";
marginTop = (index - selectedButtonIndex) * 65 + marginTop;
menuIndicatorElement.style.marginTop = marginTop + "px";
menuButtonElements.forEach(value => {
value.setAttribute('style', `
color: var(--color-surface-300);
`)
})
menuButtonElements[index].setAttribute('style', `
color: white;
`)
selectedButtonIndex = index;
})
})@import url('https://fonts.googleapis.com/css2?family=Fira+Sans+Condensed:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
@import "https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Round";
body {
--color-primary-100: rgb(105, 54, 245);
--color-primary-200: rgb(128, 78, 247);
--color-primary-300: rgb(148, 100, 249);
--color-primary-400: rgb(166, 122, 251);
--color-primary-500: rgb(183, 144, 252);
--color-primary-600: rgb(199, 165, 253);
--color-surface-100: rgb(0, 0, 0);
--color-surface-200: rgb(30, 30, 30);
--color-surface-300: rgb(53, 53, 53);
--color-surface-400: rgb(78, 78, 78);
--color-surface-500: rgb(105, 105, 105);
--color-surface-600: rgb(133, 133, 133);
--color-surface-mixed-100: rgb(26, 22, 37);
--color-surface-mixed-200: rgb(40, 35, 48);
--color-surface-mixed-300: rgb(63, 58, 70);
--color-surface-mixed-400: rgb(88, 83, 94);
--color-surface-mixed-500: rgb(113, 109, 119);
--color-surface-mixed-600: rgb(140, 136, 144);
--color-primary-100-mix: 105, 54, 245;
--color-primary-500-mix: 183, 144, 252;
font-family: Fira Sans Condensed, Arial;
margin: 0;
}
.menu-sidebar-overlay {
position: fixed;
background-color: rgba(0, 0, 0, 0.5);
z-index: 200;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
color: white;
}
.menu-sidebar {
position: relative;
height: 100%;
width: 35vw;
background-color: var(--color-primary-100);
display: flex;
}
.menu-sidebar .rectangle {
width: 15%;
height: 100%;
background-color: var(--color-surface-300);
}
.menu-sdbr-title {
display: inline;
color: var(--color-primary-200);
font-size: 40px;
margin: 15px 0 0 10px;
color: white;
}
.menu-sdbr-contents {
width: 100%;
position: relative;
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
margin-left: 10px;
}
.menu-sdbr-list {
position: absolute;
width: 100%;
padding: 0;
margin: 90px 0 0 0;
list-style-type: none;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 5px;
flex: 1;
height: 100%;
}
.menu-sdbr-list-indicator {
display: none;
width: 100%;
height: 60px;
background-color: var(--color-surface-300);
z-index: 250;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
transition: margin-top 150ms;
}
.menu-sdbr-list-item {
width: 100%;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
}
.menu-sdbr-list-item button {
position: relative;
background-color: transparent;
border: 0 solid rgba(0, 0, 0, 1);
width: 100%;
padding: 0 15px;
height: 60px;
text-align: left;
display: flex;
align-items: center;
color: var(--color-surface-300);
z-index: 300;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
font-size: 15px;
font-weight: 400;
z-index: 300;
/*
border: 1px solid rgba(0, 0, 0, 1);
border-right: none;
*/
transition: color 150ms;
}
.menu-sdbr-list-item button .material-icons {
margin-right: 15px;
font-size: 20px;
}<nav class = "menu-sidebar-overlay">
<div class = "menu-sidebar">
<div class = "menu-sdbr-contents">
<h1 class = "menu-sdbr-title">MENU</h1>
<div class = "menu-sdbr-list-indicator" id = "menu-sdbr-list-indicator">
</div>
<ul class = "menu-sdbr-list" id = "menu-sdbr-list">
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">dark_mode</span>
Dark Mode
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">settings</span>
Settings
</button>
</li>
<li class = "menu-sdbr-list-item" id = "menu-sdbr-list-item">
<button>
<span class = "material-icons">logout</span>
Log Out
</button>
</li>
</ul>
</div>
<div class = "rectangle"></div>
</div>
</nav>
Наличие этого
<div>в качестве дочернего элемента элемента<ul>является недопустимым HTML.