惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
C
Cisco Blogs
The Hacker News
The Hacker News
T
Tor Project blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
The GitHub Blog
The GitHub Blog
A
Arctic Wolf
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
The Register - Security
The Register - Security
云风的 BLOG
云风的 BLOG
Simon Willison's Weblog
Simon Willison's Weblog
P
Palo Alto Networks Blog
Vercel News
Vercel News
C
CERT Recently Published Vulnerability Notes
I
InfoQ
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
M
MIT News - Artificial intelligence
I
Intezer
aimingoo的专栏
aimingoo的专栏
U
Unit 42
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LINUX DO - 热门话题
Microsoft Security Blog
Microsoft Security Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Cyberwarzone
Cyberwarzone
P
Proofpoint News Feed
P
Proofpoint News Feed
B
Blog
T
Threat Research - Cisco Blogs
博客园 - 叶小钗
Recorded Future
Recorded Future
Last Week in AI
Last Week in AI
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Know Your Adversary
Know Your Adversary
Engineering at Meta
Engineering at Meta
G
Google Developers Blog
PCI Perspectives
PCI Perspectives
Google DeepMind News
Google DeepMind News
WordPress大学
WordPress大学
Application and Cybersecurity Blog
Application and Cybersecurity Blog
MyScale Blog
MyScale Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Schneier on Security
Schneier on Security
N
News | PayPal Newsroom
C
Cybersecurity and Infrastructure Security Agency CISA
H
Help Net Security
博客园 - 聂微东
H
Hackread – Cybersecurity News, Data Breaches, AI and More
G
GRAHAM CLULEY

Leon.D’s Blog

使用Element.animate添加文字与图片动画 Howdz起始页开发记录 利用G渲染器实现的音频可视化方案 Vue3实现自定义右键菜单 Dashboard自定义面板项目介绍 开发Online Code Editor过程的一些记录 Howdyjs组件库迁移Vue3设计与总结 使用D3.js将离散的点形成平滑曲线及其应用 使用Taro-next将个人网盘功能扩展到微信小程序 Vue3上手, 使用vite实现一个简单的Todolist应用 断点续传与个人网盘系统的前后端设计 个人组件库展示站点搭建总结 Github Webhooks与Koa2实现简单的前端自动化部署 浏览器导航首页设计 简易电商系统开发记录(Nuxtjs+Koa) 考试系统设计总结
CSS实现判断鼠标进入方向
2020-03-21 · via Leon.D’s Blog

使用纯 CSS 判断鼠标进入方向执行不一样的方向动画,或搭配 JS 实现同时记录进入方向与移出方向

目录

  1. 纯 CSS 实现
    • Clip-path
    • Transform
  2. JS 实现
    • Demo 展示
    • 判断方向算法
    • 移入方向判断
    • 移出方向判断
  3. 总结

纯 CSS 实现

实现纯 CSS 判断鼠标进入方向,主要是先通过将 DIV 以对角线切割为 4 个部分,然后即可为这 4 个部分写入:hover 选择器执行不同方向的动画。如果 DIV 是正方形的话,对角线切割就很简单,可以用伪元素通过 rotate(45deg)就可以实现。但是当 Div 是长方形的时候,就需要使用以下的方法了。

对角线切割DIV

目前可以通过 2 种 CSS3 的方式实现

  1. 使用 CSS3 的 clip-path 属性定向裁剪区域
  2. 使用 CSS3 的 transfrom 属性,将 div 倾斜再旋转一定角度实现,需要同时用到 rotate 和 skew2 种 2D 变换。

Clip-path

Demo

clip-path 方式实现主要代码逻辑

CSS

.box {
  width: 400px;
  height: 300px;
  background: #eee;
  position: relative;
  overflow: hidden;
  border: 2px solid #262626;
}
.top,
.right,
.bottom,
.left {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  transition: all 0.4s;
}
.top:after,
.right:after,
.bottom:after,
.left:after {
  position: absolute;
  content: "";
  width: 100%;
  height: 100%;
}
.top {
  top: -100%;
  background: rgb(255, 228, 177);
}
.right {
  right: -100%;
  left: auto;
  background: rgb(200, 177, 255);
}
.bottom {
  bottom: -100%;
  top: auto;
  background: rgb(177, 255, 225);
}
.left {
  left: -100%;
  background: rgb(177, 189, 255);
}
.top:after {
  clip-path: polygon(0 0, 50% 50%, 100% 0);
  top: 100%;
  left: 0;
}
.right:after {
  clip-path: polygon(100% 0, 50% 50%, 100% 100%);
  right: 100%;
  top: 0;
}
.bottom:after {
  clip-path: polygon(0 100%, 50% 50%, 100% 100%);
  bottom: 100%;
  left: 0;
}
.left:after {
  clip-path: polygon(0 0, 50% 50%, 0 100%);
  left: 100%;
  top: 0;
}
.top:hover {
  top: 0;
}
.right:hover {
  right: 0;
}
.bottom:hover {
  bottom: 0;
}
.left:hover {
  left: 0;
}
/* 解决层级阻断问题 */
.top:hover ~ .right,
.top:hover ~ .bottom,
.top:hover ~ .left,
.right:hover ~ .bottom,
.right:hover ~ .left,
.bottom:hover ~ .left {
  display: none;
}

HTML

<div class="box">
  <span>Hover Here</span>
  <div class="top">From Top</div>
  <div class="right">From Right</div>
  <div class="bottom">From Bottom</div>
  <div class="left">From Left</div>
</div>

为.top 的 Div 创建一个伪元素,然后使用 clip-path: polygon(0 0, 50% 50%, 100% 0) 定位左上角、中心、右上角切割出上部分的三角形,这时.top 实际区域是一个五边形(.top:hover 时其伪元素也会包含在其中)。

Transform

Demo

transform 实现方式与 clip-path 方式基本差不多,主要是先将伪类通过旋转加偏移变换成一个平行四边形,将变换顶点定位在中心,然后隐藏超出部分即可。

transform变换DIV

transform 方式实现主要代码逻辑 (仅列举与 clip-path 不一样的代码)

body {
  --angle: 37deg;
}

/*...省略部分...*/

.top:after,
.right:after,
.bottom:after,
.left:after {
  position: absolute;
  content: "";
  width: 100%;
  height: 100%;
  transform-origin: 0 0;
}
.top:after {
  top: 150%;
  left: 50%;
  transform: rotate(calc(var(--angle) - 180deg)) skew(
      calc((var(--angle) - 45deg) * 2)
    );
}
.right:after {
  top: 50%;
  left: -50%;
  transform: rotate(calc(0deg - var(--angle))) skew(
      calc((45deg - var(--angle)) * 2)
    );
}
.bottom:after {
  top: -50%;
  left: 50%;
  transform: rotate(var(--angle)) skew(calc((var(--angle) - 45deg) * 2));
}
.left:after {
  top: 50%;
  left: 150%;
  transform: rotate(calc(180deg - var(--angle))) skew(
      calc((45deg - var(--angle)) * 2)
    );
}

/*...省略部分...*/

transform 方式最大缺点就是需要计算角度,先将变换顶点改为 0 0(原为 50% 50%),就可以直接套用以上的变换公式,再使用 acrtan(高/宽)计算出角度,然后赋值给—angle 自定义 CSS 变量即可。

PS: calc 计算函数中值为 0 时也要带上单位,不然无法生效

JS 实现

Demo 展示

判断方向算法

以上的 Demo 中,为每个 DIV 加入了 mouseenter 和 mouseleave 的事件监听,通过判断方向后然后为其添加不同方向的移入动画和移出动画。

JS 实现判断进入方向,依然是将 div 按对角线切割成 4 个三角形,然后通过判断鼠标事件中移入的点是否在三角形内来确定方向。这里涉及到了一个如何判断点在区域内的算法实现。

判断点是否在同侧

以下提供一个函数判断点 P 和点 O 是否在直线 AB 的同一侧

/* 判断两点是否位于直线同一侧
 * @param {Object} p - P点坐标
 * @param {Object} o - O点坐标
 * @param {Object} a - A点坐标(直线AB)
 * @param {Object} b - B点坐标(直线AB)
 * @return {Boolean}
 */
function isSameSide(p, o, a, b) {
  return (
    ((p.x - a.x) * (b.y - a.y) - (p.y - a.y) * (b.x - a.x)) *
      ((o.x - a.x) * (b.y - a.y) - (o.y - a.y) * (b.x - a.x)) >=
    0
  );
}

该函数需要传入 4 个点坐标(含有 x,y 属性的对象),参数 1 和参数 2 是需要判断的两点,参数 3 和参数 4 是确定直线的两点。使用的是矢量法,过程无需用到三角函数,而且参数 1 和参数 2 顺序可互换,参数 3 和参数 4 顺序也可互换。判断点在三角形内,就需要用 3 次这个函数分别为三条边进行判断。

移入方向判断

移入方向

如图,点 P 为鼠标事件移入获取到的点,当点 P 与点 O 在直线 AB 同一侧、点 P 与点 A 在直线 OB 同一侧、点 P 与点 B 在直线 AO 同一侧,即可判断出点 P 在三角形 AOB 内,即鼠标从上方进入。转换为代码即可写成:

...
if (isSameSide(p,o,a,b) && isSameSide(p,a,o,b) && isSameSide(p,b,a,o)) {
  console.log('Slide From Top')
}
...

移出方向判断

同理,要判断移出方向,可以判断点 P 与点 O 不在直线 AB 同一侧、点 P 与点 A 是否在直线 OB 同一侧、点 P 与点 B 是否在直线 AO 同一侧,即可判断出鼠标从上方移出。转换为代码即可写成:

...
if (!isSameSide(p,o,a,b) && isSameSide(p,a,o,b) && isSameSide(p,b,a,o)) {
  console.log('Slide Out Top')
}
...

下面提供 Demo 实现的主要 JS 代码,由于 CSS 需要定义几个简单的 Slide 动画,代码比较简单,由于篇幅有限就不在本文列出,感兴趣的可以直接查看 Demo 源码。

var isSameSide = function (p, o, a, b) {
  return (
    ((p.x - a.x) * (b.y - a.y) - (p.y - a.y) * (b.x - a.x)) *
      ((o.x - a.x) * (b.y - a.y) - (o.y - a.y) * (b.x - a.x)) >=
    0
  );
};
var boxList = Array.prototype.slice.call(document.querySelectorAll(".box"));
var mouseenterEvent = function (e) {
  var el = e.currentTarget;
  var { top, left, width, height } = el.getBoundingClientRect();
  var a = { x: left, y: top };
  var b = { x: left + width, y: top };
  var c = { x: left + width, y: top + height };
  var d = { x: left, y: top + height };
  var o = { x: left + width / 2, y: top + height / 2 };
  var p = { x: e.x, y: e.y };
  if (
    isSameSide(p, o, a, b) &&
    isSameSide(p, a, o, b) &&
    isSameSide(p, b, o, a)
  ) {
    el.setAttribute("class", "box slide-from-top");
  } else if (
    isSameSide(p, o, b, c) &&
    isSameSide(p, b, o, c) &&
    isSameSide(p, c, b, o)
  ) {
    el.setAttribute("class", "box slide-from-right");
  } else if (
    isSameSide(p, o, c, d) &&
    isSameSide(p, c, o, d) &&
    isSameSide(p, d, o, c)
  ) {
    el.setAttribute("class", "box slide-from-bottom");
  } else if (
    isSameSide(p, o, a, d) &&
    isSameSide(p, a, o, d) &&
    isSameSide(p, d, o, a)
  ) {
    el.setAttribute("class", "box slide-from-left");
  } else {
    el.setAttribute("class", "box slide-from-top");
  }
};
var mouseleaveEvent = function (e) {
  var el = e.currentTarget;
  var { top, left, width, height } = el.getBoundingClientRect();
  var a = { x: left, y: top };
  var b = { x: left + width, y: top };
  var c = { x: left + width, y: top + height };
  var d = { x: left, y: top + height };
  var o = { x: left + width / 2, y: top + height / 2 };
  var p = { x: e.x, y: e.y };
  if (
    !isSameSide(p, o, a, b) &&
    isSameSide(p, a, o, b) &&
    isSameSide(p, b, o, a)
  ) {
    el.setAttribute("class", "box slide-out-top");
  } else if (
    !isSameSide(p, o, b, c) &&
    isSameSide(p, b, o, c) &&
    isSameSide(p, c, b, o)
  ) {
    el.setAttribute("class", "box slide-out-right");
  } else if (
    !isSameSide(p, o, c, d) &&
    isSameSide(p, c, o, d) &&
    isSameSide(p, d, o, c)
  ) {
    el.setAttribute("class", "box slide-out-bottom");
  } else if (
    !isSameSide(p, o, a, d) &&
    isSameSide(p, a, o, d) &&
    isSameSide(p, d, o, a)
  ) {
    el.setAttribute("class", "box slide-out-left");
  } else {
    el.setAttribute("class", "box slide-out-top");
  }
};
for (var i = 0; i < boxList.length; i++) {
  boxList[i].addEventListener("mouseenter", mouseenterEvent);
  boxList[i].addEventListener("mouseleave", mouseleaveEvent);
}

总结

使用纯 CSS 实现判断方向会有一个比较大的缺点,就是浏览器鼠标移动事件含有一定延迟,当鼠标速度很快的进入 div 时,有可能:hover 会延迟执行到后面的元素。 同时纯 CSS 方式只可判断移入方向,还未能实现可以同时判断移入和移出。需要在记录移入方向的同时,再记录移出方向目前想到的只可使用 JS 去辅助实现了。

使用 JS 实现最主要是要运用到判断两点位于直线同侧的算法,这个算法只涉及四则运算,也比较简洁。获取 DIV 四个顶点和中心的坐标可以用getBoundingClientRect()来获取元素的 top,left,width,height 计算出来。

以上内容未经授权请勿随意转载。

2021/10/01 更新, 找到了一个使用 Grid 布局实现的移入方向 Demo: CSS-only direction-aware hover effect