我正在使用 HTML/CSS/JS + jQuery 开发一个多页面 Web 应用程序。我正在尝试构建一个在鼠标悬停/鼠标移开时展开和折叠的侧边栏组件。侧边栏包含指向应用程序上其他页面的链接。展开/折叠效果很好,除了对于一个非常具体的用例,我完全被难住了。
我使用变量
mini
来跟踪侧边栏是否处于折叠(迷你)状态或展开状态。我根据 mini 是 true 还是 false 动态更新侧边栏宽度,因此当用户将鼠标移到侧边栏上时,mini = false
并且侧边栏展开......并且当用户将鼠标移出侧边栏时,mini = true
侧边栏折叠起来。
错误:
用户将鼠标悬停在侧边栏上以展开它
用户单击链接并被路由到新页面
用户将鼠标悬停在侧边栏上,这样当他们到达新页面时,鼠标仍然位于侧边栏上
现在,展开/折叠功能是反向工作的,因此当用户将鼠标远离移动时,菜单展开,而当用户将鼠标移入div时,菜单折叠并消失。令人沮丧!
我认为发生的情况是侧边栏默认在页面加载时处于折叠状态。不知何故,我需要能够确定用户在页面加载时是否将鼠标悬停在侧边栏 div 内,以便我可以相应地构建侧边栏。我非常感谢任何想法!请参阅下面的我的代码。我无法模拟页面路由,但总体思路应该很清楚。在我的代码示例中,侧边栏功能按预期工作,因此我包含了启用页面路由的实际应用程序中发生的情况的屏幕截图。见下文。谢谢!
var mini = true;
const logo = $("#nav-logo");
let activeState = false;
let activePage;
let iconsArr = [
"#dashboard-icon",
"#projects-icon",
"#assess-icon",
"#config-icon",
];
let listItemsArr = [
"#dashboard-list-item",
"#projects-list-item",
"#assess-list-item",
"#config-list-item",
];
$(function() {
attachClickListeners();
});
const toggleSidebar = () => {
if (mini) {
// sidebar
$("#mySidebar").css("width", "225px");
// main
$("#main").css("margin-left", "225px");
// logo
$("#nav-logo").attr("src", "https://www.creativefabrica.com/wp-content/uploads/2018/11/Company-business-generic-logo-by-DEEMKA-STUDIO.jpg");
$("#nav-logo").css("width", "120px");
// Logo text
$("#logo-text-container").show();
// list item styling
$(".list-item").css("padding-left", "12px");
$(".list-item").css("margin-bottom", "4px");
$(".list-item-text").show();
// Active state and page
if (activePage != undefined) {
// Remove active state from non-active items
listItemsArr.forEach((item) => {
if (item[1] != activePage[0]) {
$(item).removeClass("active");
}
});
// Add active class
$(`#${activePage}-icon`).removeClass("active");
$(`#${activePage}-list-item`).addClass("active");
}
// mini variable
this.mini = false;
} else {
// sidebar
$("#mySidebar").css("width", "60px");
// main
$("#main").css("margin-left", "60px");
// logo
$("#nav-logo").attr("src", "https://www.creativefabrica.com/wp-content/uploads/2018/11/Company-business-generic-logo-by-DEEMKA-STUDIO.jpg");
$("#nav-logo").css("width", "30px");
// logo text
$("#logo-text-container").hide();
// list item styling
$(".list-item").css("padding-left", "0px");
$(".list-item").css("margin-bottom", "6px");
$(".list-item-text").hide();
// Active state and page
if (activePage != undefined) {
// Active state and page
if (activePage != undefined) {
// Remove active state from non-active items
iconsArr.forEach((item) => {
if (item[1] != activePage[0]) {
$(item).removeClass("active");
}
});
// Add active class to active item
$(`#${activePage}-icon`).addClass("active");
$(`#${activePage}-list-item`).removeClass("active");
}
}
// mini variable
this.mini = true;
}
};
const attachClickListeners = () => {
$("#dashboard-list-item").off();
$("#dashboard-list-item").on("click", () => {
if (!activeState) {
activeState = true;
}
toggleActiveState("#dashboard-icon");
activePage = "dashboard";
});
$("#projects-list-item").off();
$("#projects-list-item").on("click", () => {
if (!activeState) {
activeState = true;
}
toggleActiveState("#projects-icon");
activePage = "projects";
});
$("#assess-list-item").off();
$("#assess-list-item").on("click", () => {
if (!activeState) {
activeState = true;
}
toggleActiveState("#assess-icon");
activePage = "assess";
});
$("#config-list-item").off();
$("#config-list-item").on("click", () => {
if (!activeState) {
activeState = true;
}
toggleActiveState("#config-icon");
activePage = "config";
});
};
const toggleActiveState = (id) => {
let element = $(id);
iconsArr.forEach((item) => {
if (item === id) {
$(element).addClass("active");
} else {
$(item).removeClass("active");
}
});
};
.active {
background: lightblue;
}
main .sidebar {
position: absolute;
top: 0;
right: 25px;
font-size: 36px;
margin-left: 50px;
}
#main {
padding: 16px;
margin-left: 85px;
transition: margin-left 0.5s;
}
body {
font-family: "Poppins", sans-serif;
font-size: 14px;
font-weight: 400;
}
.sidebar {
height: 100vh;
width: 60px;
position: fixed;
transition: 0.5s ease;
left: 0;
top: 0;
/* padding-left: 15px; */
padding-top: 9px;
background-color: #fafafa;
border-right: 1px solid #e6e6e6;
white-space: nowrap;
overflow-x: hidden;
z-index: 1;
}
#nav-logo,
#logo-text {
transition: 0.5s ease;
}
.body-text {
height: 100%;
width: 100%;
margin-left: 250px;
padding-top: 1px;
padding-left: 25px;
}
#nav-logo {
width: 30px;
margin-left: 15px;
}
#logo-text-container {
margin-left: 30px;
display: none;
}
#logo-text {
font-size: 18px;
margin-block-start: 0em;
}
.list-item {
display: flex;
align-items: center;
justify-content: flex-start;
cursor: pointer;
border: 1px solid transparent;
border-radius: 100px;
margin-bottom: 7px;
}
.list-item:hover {
background: lightblue;
}
.list-item-text {
font-size: 14px;
margin-top: 15px !important;
display: none;
}
.li-text-margin-left {
margin-left: 7px;
}
#add-assessment-list-item-text {
margin-left: 4px;
}
#projects-list-item-text {
margin-left: 1px;
}
#nav-menu-items {
padding-inline-start: 7px;
width: 206px;
transition: 0.5s ease;
}
#nav-menu-items i {
font-size: 1.2rem;
/* margin-right: 0.7rem; */
padding: 10px 10px;
border-radius: 60px;
}
.list-item-text {
margin-block-start: 0.2em;
}
<link href="https://kit.fontawesome.com/ee3b09a28a.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<!-- Sidebar -->
<div id="mySidebar" class="sidebar" onmouseover="toggleSidebar()" onmouseout="toggleSidebar()">
<!-- Expanded Logo Image -->
<div id="logo-image-container">
<img src="https://www.creativefabrica.com/wp-content/uploads/2018/11/Company-business-generic-logo-by-DEEMKA-STUDIO.jpg" id="nav-logo" />
</div>
<!-- /Expanded Logo Image -->
<!-- Expanded Logo Image Text -->
<div id="logo-text-container">
<p id="logo-text">Logo Text</p>
</div>
<!-- /Expanded Logo Image Text -->
<!-- Menu Items -->
<ul id="nav-menu-items">
<li class="list-item" id="dashboard-list-item">
<i class="fa-duotone fa-table-columns" id="dashboard-icon"></i>
<p class="list-item-text li-text-margin-left">Dashboard</p>
</li>
<li class="list-item" id="projects-list-item">
<i class="fa-duotone fa-rectangle-history-circle-user" id="projects-icon"></i>
<p class="list-item-text" id="projects-list-item-text">Projects</p>
</li>
<li class="list-item" id="assess-list-item">
<i class="fa-duotone fa-address-card" id="assess-icon"></i>
<p class="list-item-text" id="add-assessment-list-item-text">
Add Assessment
</p>
</li>
<li class="list-item" id="config-list-item">
<i class="fa-duotone fa-folder-gear" id="config-icon"></i>
<p class="list-item-text li-text-margin-left">Configuration</p>
</li>
</ul>
<!-- /Menu Items -->
</div>
<!-- /Sidebar -->
<!-- Main -->
<div id="main">
<h2>Open/Collapse Sidebar on Hover</h2>
<p>Hover over any part of the sidebar to open the it.</p>
<p>To close the sidebar, move your mouse out of the sidebar.</p>
</div>
<!-- /Main -->
这是一个替代解决方案,就在您针对 @James 提出的解决方案发表评论之前,该解决方案正在开发中。作为传递参数和使用
mini
变量的替代方法,可以使用 expand
类名。
以下解决方案删除了 JavaScript 中硬编码的 CSS 样式设置,并将其全部移至 CSS,从而简化了 JS 代码。该解决方案使用 CSS Grid 作为侧边栏和主要内容的布局机制。这允许设置特定的侧边栏宽度,而无需匹配/设置主要内容元素的任何属性。您的代码示例将主元素上的
margin-left
设置为展开/折叠状态下侧边栏宽度的值。
用户将鼠标悬停在侧边栏上,这样当他们登陆新页面时,鼠标仍然位于侧边栏上 ... 不知何故,我需要能够确定用户在页面加载时是否将鼠标悬停在侧边栏 div 内,以便我可以相应地构建侧边栏。
根据以下 Stack Overflow 帖子,在页面加载时无法使用
document.elementFromPoint(x,y)
或 document.elementsFromPoint(x,y)
(即复数版本)检索鼠标/指针位置以设置初始侧边栏展开/折叠状态。指针需要移动才能触发指针移动事件
为了检索 x, y
指针坐标。
let sidebarEl;
document.addEventListener("DOMContentLoaded", init);
function init() {
sidebarEl = document.querySelector("#mySidebar");
const pageId = document.querySelector("#main").getAttribute("data-page-id");
const selector = `.list-item[data-page-id="${pageId}"]`;
const activeLiEl = sidebarEl.querySelector(selector);
if (activeLiEl) {
activeLiEl.classList.add("active");
}
// https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events
// "Pointer events are DOM events that are fired for
// a pointing device. They are designed to create a single
// DOM event model to handle pointing input devices such as
// a mouse, pen/stylus or touch (such as one or more fingers)."
sidebarEl.addEventListener("pointerenter", toggleSidebar);
sidebarEl.addEventListener("pointerleave", toggleSidebar);
sidebarEl.querySelectorAll(".list-item").forEach(el => {
el.addEventListener("click", listItemClick);
});
}
function toggleSidebar() {
if (sidebarEl.classList.contains("expand")) {
// Collapse sidebar
sidebarEl.classList.remove("expand");
} else {
// Expand sidebar
sidebarEl.classList.add("expand");
}
}
function listItemClick() {
const pageId = this.getAttribute("data-page-id");
console.log(`Go to page ${pageId}`);
if (pageId) {
//window.location.href = `/${pageId}`;
}
}
:root {
--active-bg-color: lightblue;
--hover-bg-color: #9ebfca;
}
body {
font-family: "Poppins", sans-serif;
font-size: 14px;
font-weight: 400;
}
body {
display: grid;
grid-auto-flow: column;
grid-template-columns: auto 1fr;
margin: 0;
padding: 0;
height: 100vh;
}
.sidebar {
width: 60px;
font-size: 36px;
transition: 0.5s ease;
padding-top: 9px;
background-color: #fafafa;
border-right: 1px solid #e6e6e6;
white-space: nowrap;
overflow-x: hidden;
z-index: 1;
}
.sidebar>ul {
padding: 7px;
}
#nav-logo,
#logo-text {
transition: 0.5s ease;
}
#logo-image-container {
display: flex;
}
#nav-logo {
width: 30px;
margin-left: 15px;
}
#logo-text-container {
margin-left: 30px;
display: none;
}
#logo-text {
font-size: 18px;
margin-block-start: 0em;
}
.list-item {
display: flex;
align-items: center;
justify-content: flex-start;
cursor: pointer;
border: 1px solid transparent;
border-radius: 100px;
margin-bottom: 7px;
}
.list-item:hover {
background-color: var(--hover-bg-color);
}
.list-item i {
font-size: 1.2rem;
padding: 10px 10px;
border-radius: 60px;
}
.list-item.active i {
background-color: var(--active-bg-color);
}
.list-item p {
display: none;
font-size: 14px;
margin-top: 15px !important;
margin-block-start: 0.2em;
}
/* -----------------------------------
Start sidebar expanded styles.
CSS rules override style settings previously set.
*/
.sidebar.expand {
width: 225px;
}
.sidebar.expand #nav-logo {
width: 120px;
}
.sidebar.expand #logo-text-container,
.sidebar.expand .list-item p {
display: block;
}
.sidebar.expand .list-item {
padding-left: 12px;
margin-bottom: 4px;
}
.sidebar.expand .list-item.active {
background-color: var(--active-bg-color);
}
.sidebar.expand .list-item.active i {
background-color: unset;
}
/* --------------------------------------
End sidebar expanded styles
*/
#main {
padding: 16px;
}
.body-text {
height: 100%;
width: 100%;
margin-left: 250px;
padding-top: 1px;
padding-left: 25px;
}
<link href="https://kit.fontawesome.com/ee3b09a28a.css" rel="stylesheet" />
<!-- Sidebar -->
<div id="mySidebar" class="sidebar">
<!-- Expanded Logo Image -->
<div id="logo-image-container">
<img src="https://www.creativefabrica.com/wp-content/uploads/2018/11/Company-business-generic-logo-by-DEEMKA-STUDIO.jpg" id="nav-logo" />
</div>
<!-- /Expanded Logo Image -->
<!-- Expanded Logo Image Text -->
<div id="logo-text-container">
<p id="logo-text">Logo Text</p>
</div>
<!-- /Expanded Logo Image Text -->
<!-- Menu Items -->
<ul id="nav-menu-items">
<li class="list-item" data-page-id="dashboard">
<i class="fa-duotone fa-table-columns"></i>
<p>Dashboard</p>
</li>
<li class="list-item" data-page-id="projects">
<i class="fa-duotone fa-rectangle-history-circle-user"></i>
<p>Projects</p>
</li>
<li class="list-item" data-page-id="assess">
<i class="fa-duotone fa-address-card"></i>
<p>Add Assessment</p>
</li>
<li class="list-item" data-page-id="config">
<i class="fa-duotone fa-folder-gear"></i>
<p>Configuration</p>
</li>
</ul>
<!-- /Menu Items -->
</div>
<!-- /Sidebar -->
<!-- Main -->
<div id="main" data-page-id="dashboard">
<h1>Dashboard</h1>
<h2>Open/Collapse Sidebar on Hover</h2>
<p>Hover over any part of the sidebar to open the it.</p>
<p>To close the sidebar, move your mouse out of the sidebar.</p>
</div>
<!-- /Main -->