如何让侧边菜单栏根据鼠标位置移动并立即做出反应?

How can I get the side menu bar to move and react instantly according to mouse position?

问题

侧面菜单栏以每帧 1 个像素的速度移动并追上鼠标的位置。释放鼠标左键时,菜单停留在那里,CSS不根据'cbMenu'是否选中进行转换。我尝试过的具有此行为的浏览器在 Firefox 和 Chrome.

另外,如果我鼠标向右移动过快,菜单的左边位置会超出浏览器的左边window,也就是0,相当于设置菜单的在 CSS 中留下 属性 大于 0。由于“onmousemove”事件反应缓慢,菜单不会移动。有时菜单移动得非常快,但并非总是如此。

代码

HTML

下面的代码实际上是用PHP写的,所以我只展示了上半部分。

  <div class="header_menu_outer">
    <input type="checkbox" id="cbMenu" class="cbmenu" name=""
      value="" />
    <div id="mainmenu" class="header_menu_inner">
      <header id="header" class="header">
        <label for="cbMenu" class="menuicon_label">&#9776;
        </label>
        <h1 class="header_h1">Grayson Peddie's Course Notes</h1>
      </header>
      
      <nav id="nav" class="nav">
        <ul class="nav_menu">
          <li class="nav_menu_li">
            <a class="nav_menu_a" href="/">Home</a>
          </li>
          <?php for($i = 0; $i < count($cnmdirs); $i++) { ?>
          <li class="nav_menu_li">
            <a class="nav_menu_a" href="/<?=$cnmdirs[$i] ?>"><?=$cnmNames[$i]["title"] ?></a>
          </li>
          <?php } ?>
        </ul>
      </nav>
    </div>
  </div>

  <main id="content" class="content" role="main">
    <div class="inner_header">
      <label for="cbMenu" class="menuicon_label">&#9776;
      </label>
      <h2 class="inner_header_h2"><?=$pageTitle ?></h2>
    </div>

“”的结束标记在 MVC(模型、视图和控制器)中的不同 PHP 文件中。

CSS

@media screen and (max-width: 1200px ) {
  #cbMenu:checked + .header_menu_inner { left: 0; transition: left 0.5s linear; }
  #cbMenu + .header_menu_inner { transition: left 0.5s linear; }
  .menuicon { display: table-cell; width: 2.5em; line-height: 2.5em; text-align: center; }
  body {
    grid-template-columns: 0 auto;
  }
  .header_menu_inner { left: -400px; z-index: 10; }
}

Javascript

var mouseCX = 0; // Store the position of the mouse coordinate in X.
var mainmenu = document.getElementById("mainmenu");
var content  = document.getElementById("content");
var cbMenu = document.getElementById("cbMenu");
var menu_offsetX = 0;
var mouseDown = false;

// Get the current x-position of the mouse and store in mouseCX. "C" is "client."
document.body.onmousemove = function(event) {
    // Get the position of the mouse or touch point.
    mouseCX = (typeof event.touches !== "undefined") ? event.touches[0].clientX :
        event.clientX;
    // Only if the finger on the touchscreen or the left mouse button is held down.
    if(mouseDown)
    {
        // Don't exceed the browser's left X coordinate of 0 or higher.
        if(mainmenu.offsetLeft <= 0)
        {
            // Move the side menu according to the stored distance
            // between the mouse position and initial menu position,
            // along with the current position of the mouse or touch.
            mainmenu.style.left = (mouseCX - menu_offsetX) + 'px';
        }
    }
}

var beginMenuMovement = function()
{
    // For the first line, if the side menu bar is open and if the mouse cursor is
    // not positioned over the side menu bar, don't perform the rest of the code.
    // Or in the second line, if the menu bar is not opened and starts from the center
    // of the screen, then again, do not perform instructions in the onmousedown event.
    if((cbMenu.checked && !this.matches('#mainmenu')) ||
       (!cbMenu.checked && mouseCX > 100))
        return false;
    
    // The web page must be within 1200 pixels in width as the menu
    // is hidden by default.
    if(window.innerWidth < 1200)
    {
        mouseDown = true;
        // If the menu's left position is -400 pixels, then the
        // absolute value is 400 pixels. Add in the position of
        // the mouse in X coordinate to get the distance between
        // the menu and mouse position.
        menu_offsetX = Math.abs(mainmenu.offsetLeft) + mouseCX;
    }
}

mainmenu.onmousedown = beginMenuMovement;
content.onmousedown = beginMenuMovement;
mainmenu.ontouchstart = beginMenuMovement;
content.ontouchstart = beginMenuMovement;

var endMenuMovement = function()
{
    if(window.innerWidth < 1200)
    {
        mouseDown = false;
        
        // The entire width of the menu would be 400 pixels. Divide by 2 to
        // get 200 pixels at a negative value. If the position of the menu
        // is less than -200 pixels, the cbMenu should be left unchecked and
        // should transition back to -400 pixels. If the left position is
        // greater than -200 pixels, the cbMenu should be checked and the
        // CSS transition should begin transitioning to 0.
        // Related in CSS: transition: left 0.5s linear
        menu_minWidth = (mainmenu.offsetWidth / 2) - mainmenu.offsetWidth;
        if(mainmenu.offsetLeft > menu_minWidth)
        {
            cbMenu.checked = true;
        }
        else
        {
            cbMenu.checked = false;
        }
    }
}

mainmenu.onmouseup = endMenuMovement;
content.onmouseup = endMenuMovement;
mainmenu.ontouchend = endMenuMovement;
content.ontouchend = endMenuMovement;

预期行为

我想让菜单根据鼠标光标的位置立即做出反应。

HTML 代码解释:

滑出式菜单为那些 JavaScript 关闭的用户使用了复选框技巧。如果用户单击汉堡标签 (☰),菜单将滑出,直到用户通过单击汉堡标签关闭菜单。如果浏览 Internet 的用户在 Firefox 中使用 NoScript,这将很有用,因为 Internet 上可能有 bad/malicious 个脚本。如果没有 Javascript,用户将无法执行的唯一功能是从左向右滑动以从触摸屏一侧打开菜单。

注:

没有 jQuery 代码。 jQuery 库的文件大小过大,所以我想保持我的脚本小。当然,jQuery 很棒且易于使用,但很多人可能会忘记 jQuery 库有多大。

此外,我还包含了一个触摸标签,尽管我只专注于使用鼠标。我已经实现了单点触摸功能,但是我无法让菜单滑出。

问题

除了我的帖子标题中关于如何让菜单根据鼠标位置做出反应的问题,我如何才能让“onmouseup”的代码表现得类似于CSS 过渡?

简介

我对 CSS 和 JavaScript 进行了更改,以便在 JavaScript 开启或用户允许 [=] 时启用转换和禁用 CSS 转换49=] Firefox 的 NoScript 扩展中的地址。通过使用变换,使用 translateX() 滑入和滑出菜单比每次不断触发 mousemove 事件时调用 mainmenu.style.left 快得多。另外,我还学会了在特定点播放 CSS3 个动画直到结束。下面是 CSS 和 JavaScript.

的代码

代码

CSS

@media screen and (max-width: 1200px ) {
  #cbMenu:checked + .header_menu_inner { left: 0; transition: left 0.5s linear; }
  #cbMenu + .header_menu_inner { transition: left 0.5s linear; }
  .menuicon { display: table-cell; width: 2.5em; line-height: 2.5em; text-align: center; }
  body {
    grid-template-columns: 0 auto;
  }
  #mainmenu { left: -360px; z-index: 10; transform: translateX(0);
              box-shadow: 0 2em 2em 2em rgba(0,0,0,0.5); }
}

CSS 中发生的变化是从 .header_menu_inner#mainmenu 的变化,以及新添加的“变换”,它将 X 坐标中的平移设置为 0。JavaScript 代码删除了 .header_menu_inner class 以防止发生 CSS 转换。

JS

var mouseCX = 0; // Store the position of the mouse coordinate in X.
var prevMouseCX = 0; // Store the previous mouse coordinate in X.

// HTML Elements
var mainmenu = document.getElementById("mainmenu");
mainmenu.classList.remove("header_menu_inner");
var content  = document.getElementById("content");
var cbMenu = document.getElementById("cbMenu");

var mouseDown = false;
var distanceCX = 0;

window.onresize = (() => {
    // Skip the check if the browser window is greater than 1200 pixels
    // and the cbMenu check box is not checked.
    if(window.innerWidth > 1200 && cbMenu.checked)
    {
        cbMenu.checked = false;
        var matrixValues = menu_getTransformMatrix();
        menu_slideLeft(matrixValues[4]);
    }   
}); 

// If the menu button is toggled, open the menu. Else, close the menu.
cbMenu.onchange = function(event) {
    var matrixValues = menu_getTransformMatrix();
    if(cbMenu.checked) menu_slideRight(matrixValues[4]);
    else menu_slideLeft(matrixValues[4]);
}

// Get the current x-position of the mouse and store in mouseCX. "C" is "client."
// This allows the user to slide the side menu in and out.
var menuInMotion = function(event) {
    // If using a touch screen, this is a one-finger operation. Otherwise, the
    // user is using a mouse.
    mouseCX = (typeof event.touches !== "undefined") ? event.touches[0].clientX :
        event.clientX;
    if(mouseDown) {
        // Math.min(Math.max(min, val), max)
        // Increase or decrease the distance between the left edge of the screen
        // and where the mouse cursor is located.
        distanceCX += mouseCX - prevMouseCX;
        mainmenu.style.transform = 'translateX('
            + Math.min(Math.max(0, distanceCX), mainmenu.offsetWidth) + 'px)';
    }
    // Store the previous position of the mouse cursor. This will be used to
    // calculate the direction the mouse cursor is going.
    prevMouseCX = mouseCX;
}

document.body.onmousemove = menuInMotion;
document.body.ontouchmove = menuInMotion;

// User wants to slide the menu out from the side or slide the menu
// back towards the left. Start moving the menu.
var beginMenuMovement = function()
{
    // If the menu is open, only allow the user to swipe inside the menu bar.
    // Otherwise, if the menu is closed, the user can only slide out from the
    // left edge of the web page.
    // Only move the menu if the width of the viewport is 1200 pixels or less.
    if(window.innerWidth <= 1200
        && ((cbMenu.checked && this.matches('#mainmenu')) || mouseCX < 20))
    {
        mouseDown = true;
        content.style.userSelect = "none";
    }
}


mainmenu.onmousedown = beginMenuMovement;
content.onmousedown = beginMenuMovement;
mainmenu.ontouchstart = beginMenuMovement;
content.ontouchstart = beginMenuMovement;

// User has finished moving the side menu bar.
var endMenuMovement = function()
{
    // Same for when the user releases the left mouse button.
    if(window.innerWidth <= 1200)
    {
        mouseDown = false;
        // Get the translation values for the side bar menu.
        var matrixValues = menu_getTransformMatrix();
        // If the user slides the menu as far to the left,
        // the side menu bar is closed (unchecked).
        menu_minWidth = mainmenu.offsetWidth / 2;
        if(matrixValues[4] > menu_minWidth)
        {
            cbMenu.checked = true;
            menu_slideRight(matrixValues[4]);
        }
        else
        {
            cbMenu.checked = false;
            menu_slideLeft(matrixValues[4]);
        }
        content.style.userSelect = "";
    }
}

mainmenu.onmouseup = endMenuMovement;
content.onmouseup = endMenuMovement;
mainmenu.ontouchend = endMenuMovement;
content.ontouchend = endMenuMovement;

// The two functions below this comment is for performing animations and for
// holding at the start or end position of the side menu bar's left coordinate.
function menu_slideRight(matrixValues)
{
    // Play the animation that starts from where the user releases the mouse
    // button and slide to the end, which equals the width of the menu.
    // Source: https://developer.mozilla.org/en-US/docs/Web/API/Element/animate
    mainmenu.animate([
        { transform: 'translateX(' + matrixValues + 'px)' },
        { transform: 'translateX(' + mainmenu.offsetWidth + 'px)' }],
        { duration: 500 });
    // Set the new transform position to the width of the menu.
    mainmenu.style.transform = 'translateX(' + mainmenu.offsetWidth + 'px)';
    distanceCX = mainmenu.offsetWidth;
}

function menu_slideLeft(matrixValues)
{
    mainmenu.animate([
        { transform: 'translateX(' + matrixValues + 'px)' },
        { transform: 'translateX(0px)' }],
        { duration: 500 });
    mainmenu.style.transform = 'translateX(0px)';
    distanceCX = 0;
}

// Source: https://zellwk.com/blog/css-translate-values-in-javascript/
function menu_getTransformMatrix() {
    var matrix = window.getComputedStyle(mainmenu).transform;
    return matrix.match(/matrix.*\((.+)\)/)[1].split(', ');
}

我的代码如何工作的解释在标有 // 的注释中。

成功了吗?

到目前为止,只要启用 JavaScript,从左侧向内轻扫效果很好。如果 JavaScript 被禁用,至少我可以提供仅打开和关闭侧面菜单栏的回退。 CSS3 动画证明非常有用,因为我不必执行 for 循环。而且我不知道我可以动态创建动画!

陷阱

使用 Firefox 和 Chrome 时,从左向右滑动在 Android 中效果不佳。另外,在 Chrome for Android 中,从屏幕左边缘滑动会导致与 back/forward 手势冲突。 https://www.neowin.net/news/chrome-adds-swipe-gestures-on-android-for-going-back-and-forth-through-browser-history/

是的,触摸屏笔记本电脑的 GNOME 3 桌面环境也与 GNOME 的概览手势冲突。如果我从屏幕的左边缘滑动,而不是打开侧面菜单栏,swipe-from-left-edge 手势会激活概览,它显示的内容与 macOS 的 Expose 非常相似,但在屏幕和应用程序的右侧有动态工作区左边的图标。我想与那些从未尝试过 Linux.

的人分享有关 GNOME 3 桌面环境的信息

总结

简而言之,添加对从屏幕左边缘滑动的支持可能不是最好的主意。

但是嘿!至少我可以为那些想尝试的人分享我的代码和我的发现。

我写了一个反应库允许使用 JS 做这种动画:react-voodoo

您可以混合多个补间动画,即使是在同一个道具上,并与鼠标位置(或任何您想要的)同步播放它们

对于默认的导航器滑动行为/动作;这通常可以使用 preventDefault 函数禁用。