js事件及流程详解

W3C 规范中定义了事件的3个阶段,依次是捕获阶段、目标阶段、冒泡阶段。

事件绑定

在 JavaScript 中,有三种常用的绑定事件的方法:

在DOM中绑定

HTML 的 on[event] 事件,引号内是可执行 JS 代码。

html 复制代码
<input type="button" value="button" onclick="alertMsg()" id="btn"/>
<script>
  function alertMsg() {
    console.log('DOM绑定')
  }
</script>

另外,HTML 元素大都包含了自己的默认行为,例如:超链接、提交按钮等。我们可以通过在绑定事件中加上 return false 来阻止它的默认行为。

html 复制代码
<a href="http://www.baidu.com" onclick="return false">baidu</a>

在JS代码中绑定

js 复制代码
document.querySelector('#btn').onclick = function() {
  console.log('js绑定')
}

事件监听函数绑定

语法介绍

eleTarget.addEventListener(type, listener [, useCapture])

  • type: 表示监听事件类型的字符串,大小写敏。
  • listener: 监听回调函数, function(e){},e是事件发生产生的 Event 对象。
  • useCapture: 可选,布尔值,是否在捕获阶段触发,默认false。

第二个参数,内部的 this, 指向 DOM 节点对象 eleTarget。

js 复制代码
const btn = document.querySelector('#btn')
btn.addEventListener('click', function(e) {
  console.log(this === btn); // true
})

第二个参数,当回调函数为箭头函数时,则 this 指向 Window

js 复制代码
btn.addEventListener('click', (e) => {
  console.log(this, this === btn) //  Window { ... } false
}, false)

第三个参数,当设置为 true 则为捕获阶段触发,在冒泡阶段触发之前。

js 复制代码
btn.addEventListener('click', (e) => {
  console.log('捕获阶段触发')
}, true)
btn.addEventListener('click', (e) => {
  console.log('冒泡阶段触发')
}, false)

// 捕获阶段触发
// 冒泡阶段触发

第三个参数还可以是一个可选配置对象。

eleTarget.addEventListener(type, listener [, options])

js 复制代码
options = {
   capture: Boolean, // 是否在捕获阶段触发;默认false
   once: Boolean, // 是否只监听一次,然后自动移除;默认false,
   passive: Boolean, // 是否禁止回调函数调用 prentDefault() 方法;默认false
}

以下代码重复点击,则只会输出一次,且会出现报错信息

js 复制代码
const btn = document.querySelector('#btn')
btn.addEventListener(
'click', 
(e) => {
  console.log('冒泡阶段触发')
  e.preventDefault()
}, {
  capture: false,
  once: true,
  passive: true
})

// 冒泡阶段触发
// Unable to preventDefault inside passive event listener invocation.

相对优点

可以绑定多个事件。

为同一个目标元素添加同一个监听事件,如果回调函数也相同,那么只执行一次,其余的忽略。

js 复制代码
function alertMsg(){
  console.log('1')
}
ele.addEventListener('click', alertMsg, false)
ele.addEventListener('click', alertMsg, false)

// 输出如下 
// 1

为同一个目标元素添加同一个监听事件,如果回调函数不同,那么按照添加顺序,依次执行。

js 复制代码
function alertOne() {
  console.log(1)
}
function alertTwo() {
  console.log(2)
}
ele.addEventListener('click', alertOne, false)
ele.addEventListener('click', alertTwo, false)

// 输出如下
// 1
// 2

这点和 DOM 对象的 on[event] 事件不同,on[Event] 会出现覆盖,只最后一个起作用;而且只在冒泡阶段触发,不能指定触发的阶段。

js 复制代码
ele.onclick = function(){
  console.log(1)
}
ele.onclick = function() {
  console.log(2)
}

// 输出如下 
// 2

移除监听

eleTarget.removeEventListener(type, listener, options)

js 复制代码
function alertMsg() {
  console.log(1)
}
ele.addEventListener('click', alertMsg, false)
ele.removeEventListener('click', alertMsg, false)

removeEventListener方法作用的element和后面的参数必须与监听函数完全一致。且第二个参数必须是函数的变量形式传递;否则,相当于一个新的函数,即使内容一样,也是一个新的函数。

自定义监听

手动触发

js 复制代码
eleTarget.dispatch(event)

手动触发监听函数,通过代码触发;参数不能为空,必须是 Event 对象;返回一个布尔值,表示是否触发成功;返回值在监听函数执行后即返回。

示例:

js 复制代码
ele.addEventListener('click', () => {
  console.log('clicked')
})
console.log(ele.dispatchEvent(new Event('click')))

// 输出如下
// clicked
// true

可以自定义一个事件监听函数,在需要的位置触发函数调用。

js 复制代码
ele.addEventListener('custom3', () => {
  console.log('custom3 event trigger')
}, false)
ele.addEventListener('custom5', () => {
  console.log('custom5 event trigger')
}, false)

for(let i = 0;i < 10;i++) {
    if(i === 3) {
      ele.dispatchEvent(new Event('custom3'))
    }
    if(i === 5) {
      ele.dispatchEvent(new Event('custom5'))
    }
}

// 输出如下
// custom3 event trigger
// custom5 event trigger

Event 事件

上面的监听和触发函数的基础是 Event 对象。Event 是 js 的原生对象,每个 DOM 事件产生的对象都是它的实例。

它是一个构造函数,实例化可以自定义一个事件。

const event = new Event(typeArg, eventInit)

  • typeArg 表示所创建事件的名称。
  • eventInit 是 EventInit 类型的字典。

eventInit 对象有两个属性:

js 复制代码
const eventInit = {
  bubbles: Boolean, // 可选,指定事件是否可以冒泡;默认false,只在捕获阶段触发。
  cancelable: Boolean, // 可选,指定事件是否可以取消;默认值为 false。
}

创建一个支持冒泡且不能被取消的 look 事件

js 复制代码
const ev = new Event("look", { bubbles: true, cancelable: false });
ele.dispatchEvent(ev);

CustomEvent 事件

Event 事件的构造函数的区别是:触发时可以通过 detail 属性传入自定义数据。

js 复制代码
const customEvent = new CustomEvent('custom', {
    bubbles: false,
    cancelable: false,
    detail: { name: 'caoyuan' }
})
document.addEventListener('custom', (e) => {
    console.log('custom happened', e.detail)
}, false)
document.dispatchEvent(customEvent)

// 输出如下
// custom happened {name: 'caoyuan'}
// true

事件委托

事件委托就是利用冒泡的原理,把事件加到父元素或祖先元素上,触发执行效果。

html 复制代码
<input type="button" value="click me" id="ele" />

<script>
  const ele = document.querySelector("#ele" )
  document.onclick = function (event){
    if (event.target == ele){
      console.log(ele.value)
    }
  }
</script>

// 输出如下
// click me

事件委托可以动态的添加子元素,不需要因为元素的改动而修改事件绑定。

传统写法

html 复制代码
<ul>
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
</ul>

<script>
  const domUl = document.querySelector( "ul" )

  const item = domUl.querySelectorAll( "li" )
  for ( let i=0; i<item.length; i++){
    item[i].onclick = function (){
      console.log(item[i].innerText);
    }
  }

  const node=document.createElement( "li" )
  const textnode=document.createTextNode( "item4" )
  node.appendChild(textnode)
  domUl.appendChild(node)
</script>

点击 item1 到 item3 都有事件响应,但是点击 item4 时,没有事件响应。说明传统的事件绑定无法对动态添加的元素而动态的添加事件。

事件委托

js 复制代码
const domUl = document.querySelector( "ul" )

domUl.addEventListener('click', (e) => {
  console.log(e.target.innerText)
}, false)

const node=document.createElement("li")
const textnode=document.createTextNode("item4")
node.appendChild(textnode)
domUl.appendChild(node)

当点击 item4 时,item4 有事件响应。说明事件委托可以为新添加的 DOM 元素动态的添加事件。

鼠标事件

事件 说明
mouseover 鼠标移动到监听该事件的元素或子元素时触发(同一区域无论怎么移动就只触发一次)
mousemove 鼠标在某个元素上移动时持续触发
mouseout 鼠标离开监听该事件的元素或子元素时触发
mousedown 单击任意一个鼠标按键时触发
mouseup 松开鼠标任意一个按键时触发
mouseenter 在鼠标光标从元素外部首次移动至元素范围内触发,不参与冒泡
mouseleave 鼠标移出

鼠标按键值

DOM3标准规定 click 事件只能监听左键, 只能通过 mousedownmouseup 来判断鼠标键。

event.button 来区别鼠标的按键。

  • 0:鼠标左键
  • 1:滑轮
  • 2:鼠标右键
js 复制代码
ele.onmousedown = function (event) {
  if(event.button == 0){
    console.log('左键');
  } else if(event.button == 1){
    console.log('滑轮');
  } else if(event.button == 2){
    console.log('右键');
  } 
}

只读属性 MouseEvent.buttons 指示事件触发时哪些鼠标按键被按下。

每一个按键都用一个给定的数(见下文)表示。如果同时多个按键被按下,buttons 的值为各键对应值做与计算(+)后的值。例如,如果右键(2)和滚轮键(4)被同时按下,buttons 的值为 2 + 4 = 6。

  • 0:没有按键或者是没有初始化
  • 1:鼠标左键
  • 2:鼠标右键
  • 4:鼠标滚轮或者是中键
  • 8:第四按键 (通常是"浏览器后退"按键)
  • 16:第五按键 (通常是"浏览器前进")

鼠标右键菜单

contextmenu 事件会在用户尝试打开上下文菜单时被触发。该事件通常在鼠标点击右键或者按下键盘上的菜单键时被触发。

如下代码,则对整个页面禁用鼠标右键菜单

js 复制代码
document.addEventListener('contextmenu', function(e){
  e.preventDefault()
})

但是还是可以使用 ctrl + c 来达到复制文字的目的。

单双击事件

事件 说明
click 单击鼠标左键触发
dblclick 双击鼠标左键触发

首先,来了解一下点击事件发生的先后顺序:

  • 单击:mousedown, mouseup, click
  • 双击:mousedown, mouseup, click, mousedown, mouseup, click, dblclick

由于鼠标双击时每一次触发双击事件都会引起两次单击事件和一次双击事件,原生的 js 未专门提供与单击事件互不影响的双击事件。

对同一个元素监听单击和双击的业务处理,则会出现如下问题。

js 复制代码
ele.addEventListener('click', onDocumentClick);
ele.addEventListener('dblclick', onDocumentDblClick);

function onDocumentClick(event) {
  console.log("鼠标单击");
}
function onDocumentDblClick(event) {
  console.log("鼠标双击");
}

// 鼠标单击输出  
> "鼠标单击"

// 鼠标双击输出  
> "鼠标单击"
> "鼠标单击"
> "鼠标双击"

所以双击时为了屏蔽单击事件,引入定时器 setTimeout 功能,动态的为每次鼠标单击计时,300ms 内鼠标再次点击会触发双击事件而不走单击事件

js 复制代码
let timer;

ele.addEventListener('click', onDocumentClick);
ele.addEventListener('dblclick', onDocumenDblClick);

function onDocumentClick(event) {
  // 取消上次延时未执行的方法
  clearTimeout(timer);
  //执行延时
  timer = setTimeout(function() {
    //此处为单击事件要执行的代码
    console.log("鼠标单击");
  }, 200);
}

function onDocumenDblClick(event) {
  clearTimeout(timer);
  console.log("鼠标双击");
}

进一步只对 click 事件进行监听,则代码如下

js 复制代码
let previous=0;
let timer;
ele.addEventListener('click', function(e) {
  const diff = e.timeStamp - previous;
  if (diff < 200) {// 双击
    console.log('鼠标双击');
    clearTimeout(timer);
  } else {
    timer = setTimeout(function() {
      console.log('鼠标单击')
    },200);
    previous = e.timeStamp;    
  }
},false);

mouseover和mouseenter区别

mouseover(鼠标覆盖),mouseenter(鼠标进入)。

差异点:mouseenter 不会冒泡,简单的说,它不会被它本身的子元素的状态影响到。但是 mouseover 就会被它的子元素影响到。在触发子元素的时候,mouseover 会冒泡触发它的父元素(想要阻止 mouseover 的冒泡事件就用 mouseenter)。

共同点:当二者都没有子元素时,二者的行为是一致的。

具体异同如下代码所示:

js 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style type="text/css">
    .outer {
      width: 200px;
      height: 200px;
      border: 1px solid silver;
    }
    .inner {
      width: 100px;
      height: 100px;
      border: 1px solid blue;
    }
  </style>
</head>
<body>
  <div class="outer div1"></div>
  <div class="outer div2">
    <div class="inner">inner</div>
  </div>
  <script>
    const dom1 = document.querySelector('.div1')
    const dom2 = document.querySelector('.div2')
    const domInner = document.querySelector('.inner')

    dom1.addEventListener('mouseenter', () => {
      console.log('div1 mouseenter')
    }, false)
    dom1.addEventListener('mouseover', () => {
      console.log('div1 mouseover')
    }, false)
    dom2.addEventListener('mouseenter', () => {
      console.log('div2 mouseenter')
    }, false)
    dom2.addEventListener('mouseover', () => {
      console.log('div2 mouseover')
    }, false)
    domInner.addEventListener('mouseenter', () => {
      console.log('inner mouseenter')
    }, false)
    domInner.addEventListener('mouseover', () => {
      console.log('inner mouseover')
    }, false)
  </script>
</body>
</html>

拖拽触发click事件

场景:我在做一个拖拽方法,但是我不希望在拖拽的时候触发容器内部的click事件业务流程。

click事件触发流程:mousedown => mouseup => click

拖拽行为事件流程:mousedown => mousemove => mouseup

拖拽行为会触发click事件,它们的执行顺序是:mousedown => mousemove => mouseup => click

mousedown 按下,最终实际可能是点击行为,也可能是拖拽行为。

解决方案:可以用时间差得到当前是点击事件还是拖拽事件。

js 复制代码
let isClick = false;
let firstTime = 0;

ele.onmousedown = (e) => {
  console.log('onmousedown')
  firstTime = e.timeStamp
}
ele.onmousemove = () => {
  console.log('onmousemove')
}
ele.onmouseup = (e) => {
  console.log('onmouseup')
  if(e.timeStamp - firstTime < 200) {
    isClick = true
  }
}
ele.onclick = () => {
  if(isClick) {
    console.log('onclick')
    isClick = false
  } else {
    console.log('drag')
  }
}

touchstart触发click事件

根据Google开发者文档中的描述:移动设备上的浏览器将会在 click 事件触发时延迟 300ms,以确保这是一个"单击"事件而非"双击"事件。

而对于 touchstart 事件而言,则会在用户手指触碰屏幕的一瞬间触发所绑定的事件。所以,使用 touchstart 替换 click 事件的意义在于,帮助用户在每次点击时节省 300ms 的时间。在页面频繁需要点击,或者点击发生在动画中,对动画流畅度有较高要求的情境下,使用这种技术是非常有必要的。

在移动端,手指点击一个元素,会经过:touchstart =》 touchmove =》 touchend =》 click

touchstart 与 click 同时触发产生冲突的原因,是我们给某个元素同时绑定 touchstart 和 click 事件,这两个事件在移动设备上会发生冲突。

解决方案1:

使用事件对象中的 preventDefault 方法,preventDefault 方法的作用在于:阻止元素默认事件行为的发生,但有意思的是,当我们在目标元素同时绑定 touchstart 和 click 事件时,在 touchstart 事件回调函数中使用该方法,可以阻止后续 click 事件的发生。

js 复制代码
ele.addEventListener("touchstart", e => { 
  e.preventDefault()
  console.log("touchstart event!")
})
ele.addEventListener("click", e => { 
  console.log("click event!")
})

但是问题又来了,添加 event.preventDefault() 浏览器就会报错,click 事件依然被触发。

解决办法:

1、注册处理函数时,用如下方式,明确声明为不是被动的

js 复制代码
ele.addEventListener("touchstart", e => {
  e.preventDefault()
  console.log("touchstart event!")
}, { passive: false })

2、基于功能检测绑定事件

我们可以通过判断浏览器是否支持 touchstart 事件来封装元素的点击事件,这样客户端会根据当前环境判定元素应该绑定的事件类型,代码如下:

js 复制代码
const eventName = (() => {
  if ('ontouchstart' in document.documentElement)
    return 'touchstart'
  else
    return 'click'
})()

ele.addEventListener(eventName, () => {
  console.log(eventName)
}, false)

点击穿透

点击穿透是指在使用移动设备浏览网页时,当用户点击屏幕上的某一元素时,点击事件会同时触发下方的元素。这种现象通常发生在设计中使用了透明或半透明背景的元素上。比如:遮罩层、弹窗、浮层等。

为什么会有点击穿透?因为在 touchstart 触发后,会在300ms后触发一个 click 的默认行为。

相邻关系元素

点击穿透代码演示

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style type="text/css">
    .content {
      height: 100px;
      background: green;
    }
    .mask {
      display: none;
      position: absolute;
      top:0;
      left:0;
      width: 100%;
      height: 100px;
      background: rgba(0, 0, 0, 0.5);
    }
  </style>
</head>
<body>
  <div class="content">content</div>
  <div class="mask"></div>
  <input type="button" value="button" id="btn" />

  <script>
    const mask = document.querySelector('.mask')
    const content = document.querySelector('.content')
    const btn = document.querySelector('#btn')
    btn.ontouchstart = function(){
      mask.style.display = 'block'
    }
    mask.addEventListener('touchstart', () => {
      mask.style.display = 'none'
    }, false)
    content.addEventListener('click', () => {
      console.log('click happened')
    }, false)
  </script>
</body>
</html>

在h5端运行该网页,你会体验到什么是点击穿透。

解决这个问题的各类方案如下所述。

方案一:阻止默认事件的触发

js 复制代码
mask.addEventListener('touchstart', (e) => {
  e.preventDefault()
  mask.style.display = 'none'
}, false)

方案二:让消失元素的底部元素 在 300ms 左右暂时失去click监听功能

js 复制代码
mask.addEventListener('touchstart', (e) => {
  content.style.pointerEvents = 'none' // 让元素不再具有对鼠标事件做出反应的能力
  setTimeout(() => {
    content.style.pointerEvents = 'auto'
  }, 300);
  mask.style.display = 'none'
}, false)

方案三:让要消失的元素延迟 300ms 左右消失

js 复制代码
mask.addEventListener('touchstart', (e) => {
  setTimeout(() => {
    mask.style.display = 'none'
  }, 300);
}, false)

推荐使用方案一,后面两个方案只是为了增加你对点击穿透原理的了解。

父子关系元素

一、阻止点击穿透,子元素点击时加上下面这句,阻止触发click事件行为。

js 复制代码
event.preventDefault();

或者子元素样式添加下面这句

js 复制代码
touch-action: none;

二、点击穿透到父元素,让子元素无响应,为子元素添加下面样式代码即可

js 复制代码
pointer-events: none;

pointer-events 属性可以阻止设置了该样式的容器的鼠标相关事件的触发,但是如果容器内部的元素设置了该属性的其他值,则可以继续触发。

  • auto:默认值,设置该属性链接可以正常点击访问。
  • none:元素不能对鼠标事件做出反应

完整例子代码如下:

html 复制代码
<style type="text/css">
  .father-ele{
    width: 300px;
    height: 150px;
    padding: 20px;
    margin: 20px;
    background: #5b7e91;
  }
  .child-ele{
    width: 50px;
    height: 50px;
    line-height: 50px;
    border-radius: 10px;
    background: #ec6800;
    text-align: center;
    color: #fff;
  }
  .noclick{
    pointer-events: none;  /* 子元素加上这句样式可以实现点击穿透 */
  }
</style>

<div class="father-ele">
  <p>阻止点击穿透</p>
  <div class="child-ele">点我</div>
</div>

<div class="father-ele">
  <p>形成点击穿透</p>
  <div class="child-ele noclick">点我</div>
</div>

<script>
  const domFatherEle =  document.querySelectorAll('.father-ele')
  const domChildEle =  document.querySelectorAll('.child-ele')

  for(let i = 0; i < 2; i++) {
    domFatherEle[i].addEventListener('click', () => {
      console.log("触发父元素")
    }, false)
    domChildEle[i].addEventListener('touchstart', (e) => {
      e.preventDefault(); // 子元素加上这行代码,可以阻止点击穿透
      console.log("触发子元素")
    }, false)
  }
</script>

拖动图片致mouseup丢失

做图片拖拽效果时候遇到一个问题:发现图片在鼠标 mousedown =》mousemove 后放开鼠标不触发 mouseup 事件。由于缺少了 mouseup 事件,导致一个完整的操作无法进行。但是只有div时候就没这个问题,div中有图片的时候就会出现这个问题。

目前发现两个原因:

  1. 触发了浏览器的 drag 操作,导致 mouseup 丢失。
  2. 由于鼠标离开了操作的区域,触发了 mouseleave 导致 mouseup 丢失。

第一种情况:阻止系统默认的操作触发原生 drag 操作

js 复制代码
e.preventDefault();

第二种情况:由于鼠标移到了区域外,触发了 mouseleave 操作,因此在这种情况下要监听 mouseleave 操作,当触发该操作时可以停止或者还原状态。

如下代码示例:

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style type="text/css">
    .content {
      position: absolute;
      left: 0;
      top: 0;
      width: 100px;
      height: 100px;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid silver;
    }

    .content img {
      width: 100%;
      height: auto;
    }
  </style>
</head>

<body>
  <div class="content">
    <img src="xxx" />
  </div>

  <script>
    const domContent = document.querySelector('.content')

    function mv(e) {
      console.log('mousemove')
      domContent.style.left = e.clientX - domContent.getBoundingClientRect().width / 2  + 'px'
      domContent.style.top = e.clientY - domContent.getBoundingClientRect().height / 2 + 'px'
    }

    function mp(e) {
      console.log('mouseup')
      domContent.removeEventListener('mousemove', mv, false)
      domContent.removeEventListener('mouseup', mp, false)
    }

    domContent.addEventListener('mousedown', (e) => {
      console.log('mousedown')
      e.preventDefault();
      domContent.addEventListener('mousemove', mv, false)
      domContent.addEventListener('mouseup', mp, false)
    }, false)

    domContent.addEventListener('mouseleave', (e) => {
      console.log('mouseleave')
      domContent.removeEventListener('mousemove', mv, false)
      domContent.removeEventListener('mouseup', mp, false)
    }, false)

    domContent.addEventListener('drag', (e) => {
      console.log('drag')
    }, false)
  </script>
</body>

</html>

文本选中

样式user-select

CSS 属性 user-select 控制用户能否选中文本。除了文本框内,它对被载入的内容没有影响。

  • none:元素及其子元素的文本不可选中。可以在子元素上重写 user-select 属性值启用选择。
  • text:用户可以选择文本。
  • all:当单击子元素或者上下文文本时,那么包含该子元素的父元素整体内容也会被选中。
  • auto:auto 的具体取值取决于一系列条件,具体如下
    • 在 ::before 和 ::after 伪元素上,采用的属性值是 none
    • 如果元素是可编辑元素,则采用的属性值是 contain
    • 否则,如果此元素的父元素的 user-select 采用的属性值为 all,则该元素采用的属性值也为 all
    • 否则,如果此元素的父元素的 user-select 采用的属性值为 none,则该元素采用的属性值也为 none
    • 否则,采用的属性值为 text
html 复制代码
<p>你应该可以选中这段文本。</p>
<p class="unselectable">嘿嘿,你不能选中这段文本!</p>
<p class="all">点击一次就会选中这段文本。</p>
css 复制代码
.unselectable {
  user-select: none;
}

.all {
  user-select: all;
}

如下添加css,则这个页面无法进行选择

css 复制代码
body {
	user-select: none;
}

注意此属性对 inputtextarea 无效。

selectstart事件

selectstart 事件,其触发时间为目标对象被开始选中时(即选中动作刚开始,尚未实质性被选中)。

禁止选中

该事件常使用于使目标对象"禁止变蓝",比如在很多地方当用户双击时,一些元素会变成蓝色(选中状态),而当我们要避免这种情况时就可以使用该事件。

js 复制代码
document.addEventListener('selectstart',(e) => {
  console.log('selectstart')
}, false)

禁止选中事件则是加上阻止默认行为即可

js 复制代码
document.addEventListener('selectstart', function(e){
  e.preventDefault()
})

注意此事件对 input 和 textarea 无效。

获得选中文本

一般的事件对象的 target 只能得到元素节点,而通过 selectstart 事件的 event.target 对象可以直接返回文本节点内容。

js 复制代码
document.addEventListener('selectstart', function(e){
  console.log(e.target)
})

select事件

此事件在选择 textarea 或 input 内的内容后触发。因此只有 input 和 textarea 标签支持。

html 复制代码
<input type="text" />
<script>
  const domInput = document.querySelector('input')
  domInput.addEventListener('select', function(){
    console.log('select')
  })
</script>

键盘事件

事件 说明
keydown 当键盘按键下压的时候触发
keyup 在按键被松开时触发

流程顺序:keydown =》 keyup

html 复制代码
<input placeholder="Click here, then press down a key." size="40" />
<p id="log"></p>
js 复制代码
const input = document.querySelector("input");
const log = document.getElementById("log");

input.addEventListener("keydown", logKey);

function logKey(e) {
  log.textContent += `${e.key}`;
}

焦点事件

事件 说明
focus 当某个 HTML 元素获取焦点的时候触发
blur 当某个 HTML 元素失去焦点的时候触发

自动获取焦点

html 复制代码
<input type="text" id="demo" />
js 复制代码
document.getElementById("demo").focus();

那么判断input元素是否获取了焦点怎样做呢?用 activeElement 来判断是否获取了焦点。

js 复制代码
document.activeElement.id === "demo"

怎样判断复选框是否选定。用 checked 来判断,如:

html 复制代码
<input type="checkbox" id="checkbox" onclick="getCheck()" />
js 复制代码
function getCheck(){
  console.log( document.querySelector('#checkbox').checked)
}

如果返回值为 true ,则复选框一定被选定。

触摸属性

CSS 属性 touch-action 用于设置触摸屏用户如何操纵元素的区域 (例如,浏览器内置的缩放功能)。

touch-action 属性可以被指定为:

  • 任何一个关键字 auto、none、manipulation,
  • 或零或任何一个关键字 pan-x、pan-left、pan-right,加零或任何一个关键字 pan-y、pan-up、pan-down,加可选关键字 pinch-zoom。

值如下:

  • auto

    默认值,当触控事件发生在元素上时,由浏览器来决定进行哪些操作,比如对 viewport 进行滑动、缩放等。

  • none

    当触控事件发生在元素上时,不进行任何操作。

  • pan-x

    启用单指水平平移手势。可以与 pan-y、pan-up、pan-down 或和 pinch-zoom 组合使用。

  • pan-y

    启用单指垂直平移手势。可以与 pan-x 、pan-left、pan-right 或和 pinch-zoom 组合使用。

  • manipulation

    浏览器只允许进行滚动和持续缩放操作。任何其它被 auto 值支持的行为不被支持。启用平移和缩小缩放手势,但禁用其他非标准手势,例如双击以进行缩放。禁用双击可缩放功能可减少浏览器在用户点击屏幕时延迟生成点击事件的需要。这是"pan-x pan-y pinch-zoom"(为了兼容性本身仍然有效)的别名。

  • pan-left,pan-right,pan-up,pan-down (实验性)

    启用以指定方向滚动开始的单指手势。一旦滚动开始,方向可能仍然相反。请注意,滚动"向上"(pan-up)意味着用户正在将其手指向下拖动到屏幕表面上,同样 pan-left 表示用户将其手指向右拖动。多个方向可以组合,除非有更简单的表示(例如,"pan-left pan-right"无效,因为"pan-x"更简单,而"pan-left pan-down"有效)。

  • pinch-zoom

    启用多手指平移和缩放页面。这可以与任何平移值组合。

最常见的用法是禁用元素(及其不可滚动的后代)上的所有手势,以使用自己提供的拖放和缩放行为(如地图或游戏表面)。

css 复制代码
#map {
  touch-action: none;
}

另一种常见的模式是使用指针事件处理水平平移的图像轮播,但不想干扰网页的垂直滚动或缩放。

css 复制代码
.image-carousel {
  touch-action: pan-y pinch-zoom;
}

触摸动作也经常用于完全解决由支持双击缩放手势引起的点击事件的延迟。

css 复制代码
html {
  touch-action: manipulation;
}

注意除了 none 之外, 其他几个属性的"允许某方向上的拖动", 都是指按下去后在那个可拖动方向的执行的首次拖动,只有那一次是被允许的。但若是我在 pan-up 的情况下按下去, 先向下拖了(这是能拖的), 但是我不松开手继续往左往右的乱拖一气然后再往下拖, 那就不能下拖了,可以去试一下。

测试代码如下所示:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style type="text/css">
    body {
      touch-action: none;
      /* touch-action: auto; */
      /* touch-action: pan-y pinch-zoom; */
      /* touch-action: manipulation; */
      /* touch-action: pan-x; */
    }
    div {
      margin: 300px;
      width: 100vw;
      height: 100vh;
      border: 1px solid silver;
    }
  </style>
</head>
<body>
  <div></div>
</body>
</html>
相关推荐
阿珊和她的猫3 分钟前
Vue Router中的路由嵌套:主子路由
前端·javascript·vue.js
_龙小鱼_11 分钟前
Kotlin 作用域函数(let、run、with、apply、also)对比
java·前端·kotlin
霸王蟹16 分钟前
React 19中如何向Vue那样自定义状态和方法暴露给父组件。
前端·javascript·学习·react.js·typescript
小野猫子26 分钟前
Web GIS可视化地图框架Leaflet、OpenLayers、Mapbox、Cesium、ArcGis for JavaScript
前端·webgl·可视化3d地图
shenyan~36 分钟前
关于 js:9. Node.js 后端相关
前端·javascript·node.js
uwvwko1 小时前
ctfshow——web入门254~258
android·前端·web·ctf·反序列化
所待.3831 小时前
深入解析SpringMVC:从入门到精通
前端·spring·mvc
逃逸线LOF1 小时前
CSS之精灵图(雪碧图)Sprites、字体图标
前端·css
进取星辰2 小时前
31、魔法生物图鉴——React 19 Web Workers
开发语言·javascript·ecmascript
海天胜景2 小时前
jqGrid冻结列错行问题,将冻结表格(悬浮表格)与 正常表格进行高度同步
前端