Skip to content

鼠标事件

  1. 鼠标事件类型
    1. mousedown/mouseup,在元素上点击/释放鼠标按钮。
    2. mouseover/mouseout,鼠标指针从一个元素上移入/移出。
    3. mousemove,鼠标在元素上的每个移动都会触发此事件。
    4. click,如果使用的是鼠标左键,则在同一个元素上的 mousedown 及 mouseup 相继触发后,触发该事件。
    5. dblclick,在短时间内双击同一元素后触发。如今已经很少使用了。
    6. contextmenu,在鼠标右键被按下时触发。还有其他打开上下文菜单的方式,例如使用特殊的键盘按键,在这种情况下它也会被触发,因此它并不完全是鼠标事件。
  2. 事件顺序
    1. 一个用户操作可能会触发多个事件。例如,点击鼠标左键,在鼠标左键被按下时,会首先触发 mousedown,然后当鼠标左键被释放时,会触发 mouseup 和 click
    2. 在单个动作触发多个事件时,事件的顺序是固定的。也就是说,会遵循 mousedown → mouseup → click 的顺序调用处理程序。
  3. 鼠标按钮
    1. 与点击相关的事件始终具有 button 属性,该属性允许获取确切的鼠标按钮。
    2. 通常我们不在 click 和 contextmenu 事件中使用这一属性(button),因为前者只在单击鼠标左键时触发,后者只在单击鼠标右键时触发。
    3. 不过,在 mousedown 和 mouseup 事件中则可能需要用到 event.button,因为这两个事件在任何按键上都会触发,所以我们可以使用 button 属性来区分是左键单击还是右键单击。
    4. event.button 的所有可能值
      1. 左键 (主要按键) 0
      2. 中键 (辅助按键) 1
      3. 右键 (次要按键) 2
      4. X1 键 (后退按键) 3
      5. X2 键 (前进按键) 4
    5. 还有一个 event.buttons 属性,其中以整数的形式存储着当前所有按下的鼠标按键,每个按键一个比特位。在实际开发中,很少会用到这个属性,如果有需要的话,你可以在 MDN 中找到更多细节
    6. 过时的 event.which
      1. 一些老代码可能会使用 event.which 属性来获得按下的按键。这是一个古老的非标准的方式,具有以下可能值:
        • event.which == 1 —— 鼠标左键,
        • event.which == 2 —— 鼠标中键,
        • event.which == 3 —— 鼠标右键。
  4. 组合键:shift,alt,ctrl,meta
    1. 所有的鼠标事件都包含有关按下的组合键的信息。
      • shiftKey:Shift
      • altKey:Alt(或对于 Mac 是 Opt)
      • ctrlKey:Ctrl
      • metaKey:对于 Mac 是 Cmd
    2. 如果在事件期间按下了相应的键,则它们为 true
html
<button id="button">Alt+Shift+Click on me!</button>
<script>
  button.onclick = function(event) {
    if (event.altKey && event.shiftKey) {
      alert('Hooray!');
    }
  };
</script>
//上面这个按钮仅在 Alt+Shift+click 时才有效

注意:在 Mac 上我们通常使用 Cmd 代替 Ctrl 5. 坐标:clientX/Y,pageX/Y 1. 所有的鼠标事件都提供了两种形式的坐标 1. 相对于窗口的坐标:clientX 和 clientY。 2. 相对于文档的坐标:pageX 和 pageY。 2. 相对于文档的坐标 pageX/Y 以文档的左上角为参照物,并且同一位置的坐标不随页面的滚动而改变。相对于窗口的坐标 clientX/Y 以当前窗口的左上角为参照物,并且同一位置的坐标会随着页面的滚动而改变。 6. 防止在鼠标按下时的选择 1. 双击鼠标会有副作用,在某些界面中可能会出现干扰:它会选择文本。 2. 防止复制 1. 如果我们想禁用选择以保护我们页面的内容不被复制粘贴,那么我们可以使用另一个事件:oncopy

移动鼠标:mouseover/out,mouseenter/leave

  1. 事件 mouseover/mouseout,relatedTarget
    1. 当鼠标指针移到某个元素上时,mouseover 事件就会发生,而当鼠标离开该元素时,mouseout 事件就会发生。
    2. 这些事件很特别,因为它们具有 relatedTarget 属性。此属性是对 target 的补充。当鼠标从一个元素离开并去往另一个元素时,其中一个元素就变成了 target,另一个就变成了 relatedTarget
    3. 对于 mouseover
      • event.target —— 是鼠标移过的那个元素。
      • event.relatedTarget —— 是鼠标来自的那个元素(relatedTarget → target)。
    4. mouseout 则与之相反:
      • event.target —— 是鼠标离开的元素。
      • event.relatedTarget —— 是鼠标移动到的,当前指针位置下的元素(target → relatedTarget)。
    5. relatedTarget 可以为 null
      1. 这是正常现象,仅仅是意味着鼠标不是来自另一个元素,而是来自窗口之外。或者它离开了窗口。
      2. 当我们在代码中使用 event.relatedTarget 时,我们应该牢记这种可能性。如果我们访问 event.relatedTarget.tagName,那么就会出现错误。
  2. 跳过元素
    1. 当鼠标移动时,就会触发 mousemove 事件。但这并不意味着每个像素都会导致一个事件。
    2. 浏览器会一直检查鼠标的位置。如果发现了变化,就会触发事件。
    3. 这意味着,如果访问者非常快地移动鼠标,那么某些 DOM 元素就可能被跳过
    4. 如果 mouseover 被触发了,则必须有 mouseout
      1. 在鼠标快速移动的情况下,中间元素可能会被忽略,但是我们可以肯定一件事:如果鼠标指针“正式地”进入了一个元素(生成了 mouseover 事件),那么一旦它离开,我们就会得到 mouseout
  3. 当移动到一个子元素时 mouseout
    1. mouseout 的一个重要功能 —— 当鼠标指针从元素移动到其后代时触发
    2. 根据浏览器的逻辑,鼠标指针随时可能位于单个元素上 —— 嵌套最多的那个元素(z-index 最大的那个)。
JavaScript
parent.onmouseout = function(event) {
  /* event.target: parent element */
};
parent.onmouseover = function(event) {
  /* event.target: child element (bubbled) */
};

如果我们不检查处理程序中的 event.target,那么似乎鼠标指针离开了 #parent 元素,然后立即回到了它上面。 但是事实并非如此!鼠标指针仍然位于父元素上,它只是更深入地移入了子元素。 4. 事件 mouseenter 和 mouseleave 1. 事件 mouseenter/mouseleave 类似于 mouseover/mouseout。它们在鼠标指针进入/离开元素时触发。 2. 但是有两个重要的区别: 1. 元素内部与后代之间的转换不会产生影响。 2. 事件 mouseenter/mouseleave 不会冒泡。 3. 当鼠标指针进入一个元素时 —— 会触发 mouseenter。而鼠标指针在元素或其后代中的确切位置无关紧要。 4. 当鼠标指针离开该元素时,事件 mouseleave 才会触发。 5. 事件委托 1. 事件 mouseenter/leave 非常简单且易用。但它们不会冒泡。因此,我们不能使用它们来进行事件委托。 2. 通常的解决方案是 —— 在 <table> 中设置处理程序,并在那里处理事件。但 mouseenter/leave 不会冒泡。因此,如果类似的事件发生在 <td> 上,那么只有 <td> 上的处理程序才能捕获到它。 3. <table> 上的 mouseenter/leave 的处理程序仅在鼠标指针进入/离开整个表格时才会触发。无法获取有关其内部移动的任何信息。

JavaScript
// 高亮显示鼠标指针下的元素
table.onmouseover = function(event) {
  let target = event.target;
  target.style.background = 'pink';
};

table.onmouseout = function(event) {
  let target = event.target;
  target.style.background = '';
};
  • 在变量中记住当前被高亮显示的 <td>,让我们称它为 currentElem
  • mouseover —— 如果我们仍然在当前的 <td> 中,则忽略该事件。
  • mouseout —— 如果没有离开当前的 <td>,则忽略。
JavaScript
// 现在位于鼠标下方的 <td>(如果有)
let currentElem = null;

table.onmouseover = function(event) {
  // 在进入一个新的元素前,鼠标总是会先离开前一个元素
  // 如果设置了 currentElem,那么我们就没有鼠标所悬停在的前一个 <td>,
  // 忽略此事件
  if (currentElem) return;

  let target = event.target.closest('td');

  // 我们移动到的不是一个 <td> —— 忽略
  if (!target) return;

  // 现在移动到了 <td> 上,但在处于了我们表格的外部(可能因为是嵌套的表格)
  // 忽略
  if (!table.contains(target)) return;

  // 给力!我们进入了一个新的 <td>
  currentElem = target;
  onEnter(currentElem);
};


table.onmouseout = function(event) {
  // 如果我们现在处于所有 <td> 的外部,则忽略此事件
  // 这可能是一个表格内的移动,但是在 <td> 外,
  // 例如从一个 <tr> 到另一个 <tr>
  if (!currentElem) return;

  // 我们将要离开这个元素 —— 去哪儿?可能是去一个后代?
  let relatedTarget = event.relatedTarget;

  while (relatedTarget) {
    // 到父链上并检查 —— 我们是否还在 currentElem 内
    // 然后发现,这只是一个内部移动 —— 忽略它
    if (relatedTarget == currentElem) return;

    relatedTarget = relatedTarget.parentNode;
  }

  // 我们离开了 <td>。真的。
  onLeave(currentElem);
  currentElem = null;
};

// 任何处理进入/离开一个元素的函数
function onEnter(elem) {
  elem.style.background = 'pink';

  // 在文本区域显示它
  text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`;
  text.scrollTop = 1e6;
}

function onLeave(elem) {
  elem.style.background = '';

  // 在文本区域显示它
  text.value += `out <- ${elem.tagName}.${elem.className}\n`;
  text.scrollTop = 1e6;
}

重要的功能是:

  1. 它使用事件委托来处理表格中任何 <td> 的进入/离开。因此,它依赖于 mouseover/out 而不是 mouseenter/leavemouseenter/leave 不会冒泡,因此也不允许事件委托。
  2. 额外的事件,例如在 <td> 的后代之间移动都会被过滤掉,因此 onEnter/Leave 仅在鼠标指针进入/离开 <td> 整体时才会运行。

鼠标拖放事件

在现代 HTML 标准中有一个 关于拖放的部分,其中包含了例如 dragstart 和 dragend 等特殊事件。 这些事件使我们能够支持特殊类型的拖放,例如处理从 OS 文件管理器中拖动文件,并将其拖放到浏览器窗口中。之后,JavaScript 便可以访问此类文件中的内容。 但是,原生的拖放事件也有其局限性。例如,我们无法阻止从特定区域的拖动。并且,我们无法将拖动变成“水平”或“竖直”的。还有很多其他使用它们无法完成的拖放任务。并且,移动设备对此类事件的支持非常有限。

  1. 拖放算法
    1. 在 mousedown 上 —— 根据需要准备要移动的元素(也许创建一个它的副本,向其中添加一个类或其他任何东西)。
    2. 然后在 mousemove 上,通过更改 position:absolute 情况下的 left/top 来移动它。
    3. mouseup 上 —— 执行与完成的拖放相关的所有行为。
JavaScript
	ball.onmousedown = function(event) {
	  // (1) 准备移动:确保 absolute,并通过设置 z-index 以确保球在顶部
	  ball.style.position = 'absolute';
	  ball.style.zIndex = 1000;
	
	  // 将其从当前父元素中直接移动到 body 中
	  // 以使其定位是相对于 body 的
	  document.body.append(ball);
	
	  // 现在球的中心在 (pageX, pageY) 坐标上
	  function moveAt(pageX, pageY) {
	    ball.style.left = pageX - ball.offsetWidth / 2 + 'px';
	    ball.style.top = pageY - ball.offsetHeight / 2 + 'px';
	  }
	
	  // 将我们绝对定位的球移到指针下方
	  moveAt(event.pageX, event.pageY);
	
	  function onMouseMove(event) {
	    moveAt(event.pageX, event.pageY);
	  }
	
	  // (2) 在 mousemove 事件上移动球
	  document.addEventListener('mousemove', onMouseMove);
	
	  // (3) 放下球,并移除不需要的处理程序
	  ball.onmouseup = function() {
	    document.removeEventListener('mousemove', onMouseMove);
	    ball.onmouseup = null;
	  };
	  //禁用,作用是鼠标在通知拖放时,ball不会跟着鼠标
	  ball.ondragstart = function() {
		  return false;
		};
	
	};
  1. 修正定位
    1. 更新以上代码,当访问者按下按钮(mousedown)时 —— 我们可以在变量 shiftX/shiftY 中记住鼠标指针到球左上角的距离。我们应该在拖动时保持这个距离。
JavaScript
//通过坐标相减来获取这个偏移
// onmousedown
let shiftX = event.clientX - ball.getBoundingClientRect().left;
let shiftY = event.clientY - ball.getBoundingClientRect().top

然后,在拖动球时,我们将鼠标指针相对于球的这个偏移也考虑在内,像这样

JavaScript
// onmousemove
// 球具有 position: absolute
ball.style.left = event.pageX - shiftX + 'px';
ball.style.top = event.pageY - shiftY + 'px';
  1. 潜在的放置目标
    1. 抽象地讲,我们取一个 “draggable” 的元素,并将其放在 “droppable” 的元素上。
    2. 我们需要知道;
      1. 在拖放结束时,所拖动的元素要放在哪里 —— 执行相应的行为
      2. 并且,最好知道我们所拖动到的 “droppable” 的元素的位置,并高亮显示 “droppable” 的元素。
      3. 有一个叫做 document.elementFromPoint(clientX, clientY) 的方法。它会返回在给定的窗口相对坐标处的嵌套的最深的元素(如果给定的坐标在窗口外,则返回 null)。如果同一坐标上有多个重叠的元素,则返回最上面的元素。
JavaScript
// 在一个鼠标事件处理程序中
ball.hidden = true; // (*) 隐藏我们拖动的元素

let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
// elemBelow 是球下方的元素,可能是 droppable 的元素

ball.hidden = false;

请注意:我们需要在调用 (*) 之前隐藏球。否则,我们通常会在这些坐标上有一个球,因为它是在鼠标指针下的最顶部的元素:elemBelow=ball。 我们可以使用该代码来检查我们正在“飞过”的元素是什么。并在放置(drop)时,对放置进行处理。 基于 onMouseMove 扩展的代码,用于查找 “droppable” 的元素

JavaScript
// 我们当前正在飞过的潜在的 droppable 的元素
let currentDroppable = null;

function onMouseMove(event) {
  moveAt(event.pageX, event.pageY);

  ball.hidden = true;
  let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
  ball.hidden = false;

  // mousemove 事件可能会在窗口外被触发(当球被拖出屏幕时)
  // 如果 clientX/clientY 在窗口外,那么 elementfromPoint 会返回 null
  if (!elemBelow) return;

  // 潜在的 droppable 的元素被使用 "droppable" 类进行标记(也可以是其他逻辑)
  let droppableBelow = elemBelow.closest('.droppable');

  if (currentDroppable != droppableBelow) {
    // 我们正在飞入或飞出...
    // 注意:它们两个的值都可能为 null
    //   currentDroppable=null —— 如果我们在此事件之前,鼠标指针不是在一个 droppable 的元素上(例如空白处)
    //   droppableBelow=null —— 如果现在,在当前事件中,我们的鼠标指针不是在一个 droppable 的元素上

    if (currentDroppable) {
      // 处理“飞出” droppable 的元素时的处理逻辑(移除高亮)
      leaveDroppable(currentDroppable);
    }
    currentDroppable = droppableBelow;
    if (currentDroppable) {
      // 处理“飞入” droppable 的元素时的逻辑
      enterDroppable(currentDroppable);
    }
  }
}

指针事件

指针事件(Pointer Events)是一种用于处理来自各种输入设备(例如鼠标、触控笔和触摸屏等)的输入信息的现代化解决方案。

  1. 指针事件类型
指针事件类似的鼠标事件
pointerdownmousedown
pointerupmouseup
pointermovemousemove
pointerovermouseover
pointeroutmouseout
pointerentermouseenter
pointerleavemouseleave
pointercancel-
gotpointercapture-
lostpointercapture-
在代码中用 pointer<event> 替换 mouse<event>
我们可以把代码中的 mouse<event> 都替换成 pointer<event>,程序仍然正常兼容鼠标设备。
替换之后,程序对触屏设备的支持会“魔法般”地提升。但是,我们可能需要在 CSS 中的某些地方添加 touch-action: none。我们会在下文的 pointercancel 一节中描述这里面的细节。
  1. 指针事件属性
    1. 指针事件具备和鼠标事件完全相同的属性,包括 clientX/Y 和 target 等,以及一些其他属性:
      • pointerId —— 触发当前事件的指针唯一标识符。浏览器生成的。使我们能够处理多指针的情况,例如带有触控笔和多点触控功能的触摸屏(下文会有相关示例)。
      • pointerType —— 指针的设备类型。必须为字符串,可以是:“mouse”、“pen” 或 “touch”。我们可以使用这个属性来针对不同类型的指针输入做出不同响应。
      • isPrimary —— 当指针为首要指针(多点触控时按下的第一根手指)时为 true。有些指针设备会测量接触面积和点按压力(例如一根手指压在触屏上),对于这种情况可以使用以下属性:
      • width —— 指针(例如手指)接触设备的区域的宽度。对于不支持的设备(如鼠标),这个值总是 1
      • height —— 指针(例如手指)接触设备的区域的长度。对于不支持的设备,这个值总是 1
      • pressure —— 触摸压力,是一个介于 0 到 1 之间的浮点数。对于不支持压力检测的设备,这个值总是 0.5(按下时)或 0
      • tangentialPressure —— 归一化后的切向压力(tangential pressure)。
      • tiltXtiltYtwist —— 针对触摸笔的几个属性,用于描述笔和屏幕表面的相对位置。
  2. 多点触控
    1. 多点触控(用户在手机或平板上同时点击若干个位置,或执行特殊手势)是鼠标事件完全不支持的功能之一。
    2. 指针事件使我们能够通过 pointerId 和 isPrimary 属性的帮助,能够处理多点触控。
  3. 事件:pointercancel
    1. pointercancel 事件将会在一个正处于活跃状态的指针交互由于某些原因被中断时触发。也就是在这个事件之后,该指针就不会继续触发更多事件了。
    2. 导致指针中断的可能原因如下:
      • 指针设备硬件在物理层面上被禁用。
      • 设备方向旋转(例如给平板转了个方向)。
      • 浏览器打算自行处理这一交互,比如将其看作是一个专门的鼠标手势或缩放操作等。
    3. 阻止浏览器的默认行为来防止 pointercancel 触发。
JavaScript
thumb.onpointerdown = function(event) {
  // 把所有指针事件(pointerup 之前发生的)重定向到 thumb
  thumb.setPointerCapture(event.pointerId);

  // 开始跟踪指针的移动
  thumb.onpointermove = function(event) {
    // 移动滑动条:在 thumb 上监听即可,因为所有指针事件都被重定向到了 thumb
    let newLeft = event.clientX - slider.getBoundingClientRect().left;
    thumb.style.left = newLeft + 'px';
  };

  // 当结束(pointerup)时取消对指针移动的跟踪
  thumb.onpointerup = function(event) {
    thumb.onpointermove = null;
    thumb.onpointerup = null;
    // ...这里还可以处理“拖动结束”相关的逻辑
  };
};

// 注意:无需调用 thumb.releasePointerCapture,
// 它会在 pointerup 时被自动调用
  1. 指针捕获
    1. 指针捕获(Pointer capturing)是针对指针事件的一个特性。
    2. 主要的方法是:
      • elem.setPointerCapture(pointerId) —— 将给定的 pointerId 绑定到 elem。在调用之后,所有具有相同 pointerId 的指针事件都将 elem 作为目标(就像事件发生在 elem 上一样),无论这些 elem 在文档中的实际位置是什么。
      • 换句话说,elem.setPointerCapture(pointerId) 将所有具有给定 pointerId 的后续事件重新定位到 elem
    3. 绑定会在以下情况下被移除:
      • 当 pointerup 或 pointercancel 事件出现时,绑定会被自动地移除。
      • 当 elem 被从文档中移除后,绑定会被自动地移除。
      • 当 elem.releasePointerCapture(pointerId) 被调用,绑定会被移除。
    4. 指针捕获可以被用于简化拖放类的交互。
  2. 指针捕获事件
    1. 还有两个与指针捕获相关的事件
      • gotpointercapture 会在一个元素使用 setPointerCapture 来启用捕获后触发。
      • lostpointercapture 会在捕获被释放后触发:其触发可能是由于 releasePointerCapture 的显式调用,或是 pointerup/pointercancel 事件触发后的自动调用。

键盘:keydown 和 keyup

如果我们想要跟踪 <input> 字段中的所有输入,那么键盘事件是不够的。无论如何,还需要一个名为 input 的事件来跟踪 <input> 字段中的更改。

  1. 测试台
JavaScript
kinput.onkeydown = kinput.onkeyup = kinput.onkeypress = handle;

let lastTime = Date.now();

function handle(e) {
  if (form.elements[e.type + 'Ignore'].checked) return;

  area.scrollTop = 1e6;

  let text = e.type +
    ' key=' + e.key +
    ' code=' + e.code +
    (e.shiftKey ? ' shiftKey' : '') +
    (e.ctrlKey ? ' ctrlKey' : '') +
    (e.altKey ? ' altKey' : '') +
    (e.metaKey ? ' metaKey' : '') +
    (e.repeat ? ' (repeat)' : '') +
    "\n";

  if (area.value && Date.now() - lastTime > 250) {
    area.value += new Array(81).join('-') + '\n';
  }
  lastTime = Date.now();

  area.value += text;

  if (form.elements[e.type + 'Stop'].checked) {
    e.preventDefault();
  }
}
css
#kinput {
  font-size: 150%;
  box-sizing: border-box;
  width: 95%;
}

#area {
  width: 95%;
  box-sizing: border-box;
  height: 250px;
  border: 1px solid black;
  display: block;
}

form label {
  display: inline;
  white-space: nowrap;
}
html
<!DOCTYPE HTML>
<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="style.css">
</head>

<body>

  <form id="form" onsubmit="return false">

    Prevent default for:
    <label>
      <input type="checkbox" name="keydownStop" value="1"> keydown</label>&nbsp;&nbsp;&nbsp;
    <label>
      <input type="checkbox" name="keyupStop" value="1"> keyup</label>

    <p>
      Ignore:
      <label>
        <input type="checkbox" name="keydownIgnore" value="1"> keydown</label>&nbsp;&nbsp;&nbsp;
      <label>
        <input type="checkbox" name="keyupIgnore" value="1"> keyup</label>
    </p>

    <p>Focus on the input field and press a key.</p>

    <input type="text" placeholder="Press keys here" id="kinput">

    <textarea id="area" readonly></textarea>
    <input type="button" value="Clear" onclick="area.value = ''" />
  </form>
  <script src="script.js"></script>

</body>
</html>
  1. Keydown 和 keyup
    1. 当一个按键被按下时,会触发 keydown 事件,而当按键被释放时,会触发 keyup 事件。
    2. event.code 和 event.key
      1. 事件对象的 key 属性允许获取字符,而事件对象的 code 属性则允许获取“物理按键代码”。
      2. 例如,同一个按键 Z,可以与或不与 Shift 一起按下。我们会得到两个不同的字符:小写的 z 和大写的 Z
      3. event.key 正是这个字符,并且它将是不同的。但是,event.code 是相同的:
      4. “KeyZ” 和其他按键代码 每个按键的代码都取决于该按键在键盘上的位置。UI 事件代码规范 中描述了按键代码。
        • 字符键的代码为 "Key<letter>""KeyA""KeyB" 等。
        • 数字键的代码为:"Digit<number>""Digit0""Digit1" 等。
        • 特殊按键的代码为按键的名字:"Enter""Backspace""Tab" 等。
      5. 大小写敏感:"KeyZ",不是 "keyZ"
  2. 自动重复
    1. 如果按下一个键足够长的时间,它就会开始“自动重复”:keydown 会被一次又一次地触发,然后当按键被释放时,我们最终会得到 keyup。因此,有很多 keydown 却只有一个 keyup 是很正常的。
    2. 对于由自动重复触发的事件,event 对象的 event.repeat 属性被设置为 true
  3. 默认行为
    • 出现在屏幕上的一个字符(最明显的结果)。
    • 一个字符被删除(Delete 键)。
    • 滚动页面(PageDown 键)。
    • 浏览器打开“保存页面”对话框(Ctrl+S)
    • ……等。
  4. 历史遗留
    1. 过去有一个 keypress 事件,还有事件对象的 keyCodecharCode 和 which 属性。
    2. 大多数浏览器对它们都存在兼容性问题,以致使该规范的开发者不得不弃用它们并创建新的现代的事件(上面所讲的这些事件),除此之外别无选择。旧的代码仍然有效,因为浏览器还在支持它们,但现在完全没必要再使用它们。
  5. 移动端键盘
    1. 当使用虚拟/移动端键盘时,更正式一点的名字叫做 IME(Input-Method Editor),W3C 标准规定 KeyboardEvent 的 e.keyCode 应该为 229,并且 e.key 应该为 "Unidentified"
    2. 当按下某些按键(例如箭头或退格键)时,虽然其中一些键盘可能仍然使用正确的值来表示 e.keye.codee.keyCode…,但并不能保证所有情况下都能对应正确的值。所以你的键盘逻辑可能并不能保证适用于移动设备。

滚动

scroll 事件允许对页面或元素滚动作出反应。我们可以在这里做一些有用的事情。 例如:

  • 根据用户在文档中的位置显示/隐藏其他控件或信息。
  • 当用户向下滚动到页面末端时加载更多数据
  1. 防止滚动
    1. 我们不能通过在 onscroll 监听器中使用 event.preventDefault() 来阻止滚动,因为它会在滚动发生 之后 才触发。
    2. 但是我们可以在导致滚动的事件上,例如在 pageUp 和 pageDown 的 keydown 事件上,使用 event.preventDefault() 来阻止滚动。
    3. 如果我们向这些事件中添加事件处理程序,并向其中添加 event.preventDefault(),那么滚动就不会开始。
    4. 启动滚动的方式有很多,使用 CSS 的 overflow 属性更加可靠。
  2. 无限页面
JavaScript
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>

<body>
<h1>Scroll me</h1>
<script>
  function populate() {
    while(true) {
      let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom;
      if (windowRelativeBottom > document.documentElement.clientHeight + 100) break;
      document.body.insertAdjacentHTML("beforeend", `<p>Date: ${new Date()}</p>`);
    }
  }

  window.addEventListener('scroll', populate);

  populate(); // init document
</script>

</body>
</html>