超赞Win10日历悬停效果,爱了爱了(使用HTML、CSS和vanilla JS)

超赞Win10日历悬停效果,爱了爱了(使用HTML、CSS和vanilla JS)

技术杂谈小彩虹2021-07-14 3:08:3130A+A-

这是我参与更文挑战的第4天,活动详情查看: 更文挑战

在这里插入图片描述

在本文中,我将向您解释我是如何创建自己的Windows 10悬停效果日历的

本文可能有点复杂,但这是针对初学者的,如果您已经精通JS,并且知道Grid悬停逻辑,则可以快速遍历代码以了解发生了什么。如果基础较差也没关系,建议点赞收藏日后慢慢研究

在这里插入图片描述 观察结果

1.毫无疑问, 这里使用了“网格悬停”效果,但是在光标周围的每个方向上突出显示了一个以上元素的边框,即,元素后面的元素也被突出显示了 2.日期没有按钮悬停效果 3.网格悬停效果不适用于活动日期(今天的date)元素。 4.默认情况下,活动日期在边框和背景之间有一个空格。如果选择其他日期,则消除间隔。 5.点击日期(非有效日期)只有一个彩色边框 6.活动元素的边框被照亮

入门

您可能已经猜到了,我将从网格效果代码开始。

网格的前7个元素是星期名称和休息日期。由于日历一次显示42个日期,因此我在中添加了42个win-btn元素win-grid。一些日期处于非活动状态,其中之一处于活动状态,因此我相应地添加了类。

HTML

<html>

<head>
  <title>Windows 10 calendar hover effect</title>
</head>

<body>
  <h1>Windows 10 Calendar hover effect</h1>
  <div class="win-grid">
    <p class="week" id="1">Mo</p>
    <p class="week" id="2">Tu</p>
    <p class="week" id="3">We</p>
    <p class="week" id="4">Th</p>
    <p class="week" id="5">Fr</p>
    <p class="week" id="6">Sa</p>
    <p class="week" id="7">Su</p>
    <div class="win-btn win-btn-inactive" id="40">29</div>
    <div class="win-btn win-btn-inactive" id="41">30</div>
    <div class="win-btn win-btn-inactive" id="42">31</div>
    <div class="win-btn" id="1">1</div>
    <div class="win-btn" id="2">2</div>
    <div class="win-btn" id="3">3</div>
    <div class="win-btn" id="4">4</div>
    <div class="win-btn" id="5">5</div>
    <div class="win-btn" id="6">6</div>
    <div class="win-btn" id="7">7</div>
    <div class="win-btn" id="8">8</div>
    <div class="win-btn" id="9">9</div>
    <div class="win-btn" id="10">10</div>
    <div class="win-btn" id="11">11</div>
    <div class="win-btn" id="12">12</div>
    <div class="win-btn" id="13">13</div>
    <div class="win-btn" id="14">14</div>
    <div class="win-btn" id="15">15</div>
    <div class="win-btn" id="16">16</div>
    <div class="win-btn win-btn-active" id="17">17</div>
    <div class="win-btn" id="18">18</div>
    <div class="win-btn" id="19">19</div>
    <div class="win-btn" id="20">20</div>
    <div class="win-btn" id="21">21</div>
    <div class="win-btn" id="22">22</div>
    <div class="win-btn" id="23">23</div>
    <div class="win-btn" id="24">24</div>
    <div class="win-btn" id="25">25</div>
    <div class="win-btn" id="26">26</div>
    <div class="win-btn" id="27">27</div>
    <div class="win-btn" id="28">28</div>
    <div class="win-btn" id="29">29</div>
    <div class="win-btn" id="30">30</div>
    <div class="win-btn win-btn-inactive" id="31">1</div>
    <div class="win-btn win-btn-inactive" id="32">2</div>
    <div class="win-btn win-btn-inactive" id="33">3</div>
    <div class="win-btn win-btn-inactive" id="34">4</div>
    <div class="win-btn win-btn-inactive" id="35">5</div>
    <div class="win-btn win-btn-inactive" id="36">6</div>
    <div class="win-btn win-btn-inactive" id="37">7</div>
    <div class="win-btn win-btn-inactive" id="38">8</div>
    <div class="win-btn win-btn-inactive" id="39">9</div>
  </div>
</body>

</html>

里面的CSS,我们改变的列数在网格中7,并添加以下类:win-btn-inactivewin-btn-activewin-btn-selected

CSS

@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100&display=swap");

* {
  box-sizing: border-box !important;
  color: white;
  text-transform: capitalize !important;
  font-family: "Noto Sans JP", sans-serif;
  letter-spacing: 2px;
}

body {
  background-color: black;
  display: flex;
  flex-flow: column wrap;
  justify-content: center;
  align-items: center;
}

.win-grid {
  border: 1px solid white;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-gap: 0.2rem;
  align-items: stretch;
  text-align: center;
  padding: 2rem;
  cursor: default;
}

.win-btn {
  padding: 1rem;
  text-align: center;
  border-radius: 0px;
  border: 3px solid transparent;
}

/* Today's Date */
.win-btn-active {
  background: red;
}

/* Other Month's Date */
.win-btn-inactive {
  color: #ffffff5f;
}

/* Clicked Date */
.win-btn-selected {
  border: 3px solid red;
}

button:focus {
  outline: none;
}

win-btn事件侦听器外,JS代码几乎相同。我们不再需要那些了。另外,由于我们向元素添加了更多类,因此我们不能仅直接比较className网格悬停事件中的。我们需要检查该类是否存在于元素的中classList。

JS

const offset = 69;
const angles = []; //in deg
for (let i = 0; i <= 360; i += 45) {
  angles.push((i * Math.PI) / 180);
}
let nearBy = [];

function clearNearBy() {
  nearBy.splice(0, nearBy.length).forEach((e) => (e.style.borderImage = null));
}

const body = document.querySelector(".win-grid");

body.addEventListener("mousemove", (e) => {
  const x = e.x; //x position within the element.
  const y = e.y; //y position within the element.

  clearNearBy();
  nearBy = angles.reduce((acc, rad, i, arr) => {
    const cx = Math.floor(x + Math.cos(rad) * offset);
    const cy = Math.floor(y + Math.sin(rad) * offset);
    const element = document.elementFromPoint(cx, cy);

    if (element !== null) {
      console.log("cursor at ", x, y, "element at ", cx, cy, element.id);
      if (
        element.classList.contains("win-btn") &&
        acc.findIndex((ae) => ae.id === element.id) < 0
      ) {
        const brect = element.getBoundingClientRect();
        const bx = x - brect.left; //x position within the element.
        const by = y - brect.top; //y position within the element.
        if (!element.style.borderImage)
            element.style.borderImage = `radial-gradient(${offset * 2}px ${offset * 2}px at ${bx}px ${by}px ,rgba(255,255,255,0.7),rgba(255,255,255,0.1),transparent ) 9 / 1px / 0px stretch `;
        return [...acc, element];
      }
    }
    return acc;
  }, []);
});

body.onmouseleave = (e) => {
  clearNearBy();
};

这就是我们的初始日历的样子

在这里插入图片描述

微调网格效果

如您所见,网格效果有效,但是我们需要修复一些错误并进行一些状态管理。让我们研究每个错误并讨论其解决方案。

问题1——非常接近光标的元素未突出显示

很奇怪吧!当光标非常靠近某个元素时,其目标win-grid仅是该元素,因此理想情况下必须突出显示所有附近的元素。但是这里发生了什么,您能猜出原因吗? 在这里插入图片描述

对于那些仍然没有get到它的人,该offset值大于附近的元素,因此,以蓝色显示的元素不会被突出显示!为了解决这个问题,我们需要将偏移量值减小到一个更接近的值。但是,如果偏移量小于元素的尺寸,它将如何到达附近的8个元素?

解决方案1

我们可以做的是,我们可以在每条偏移线上指定2个点,而不仅仅是定位端点。第一个点可能非常靠近中心,第二个点仅是端点。在这里插入图片描述 在撰写本文时,我才意识到还有一些优化的余地!在网格效果中,我们正在计算8个值,根据我的新方法,我们将不得不计算16个值!如您所见,我们可以跳过一些“第一点”计算,即靠近中心且主要目的是检测极近的元素的点。

因此,我们将只计算4个NearBy点,因此每次鼠标移动总共要计算12个点,而不是8个。

问题2——有效日期的边框和背景之间的差距

这听起来似乎不是一个大问题,但请考虑一下。你会怎么做?我们想到的最明显的想法是,将每个win-btn元素包装在a内div,并对外部容器元素应用边框效果。

但是这样做会增加DOM中元素的数量,此外,我们还必须更改代码中正在检测的元素。

因此,每次移动光标时,我们都会得到一个附近的win-btn元素,然后必须更改其父元素的样式。当鼠标移到容器元素上方时,我们还需要添加场景,并且对添加到我们的DOM中的新元素进行此类次要事件处理。 这样,我们只是添加了越来越多可以避免的事件侦听器。

解决方案2

有一个CSS属性,可以帮助我们准确地完成所需的操作。它被称为background-origin

background-originCSS属性设置背景的原点:从边框开始,在边框内或在填充内。默认值为border-box,这表示背景从边框结束的地方开始。

我们将使用content-box值,因为这将使我们能够使用盒子模型的填充区域作为边框和背景之间的间隙!

剩余逻辑

现在剩下的唯一事情是所选日期的次要状态处理。我们需要记住先前选择的元素,以便在选择新日期时,首先清除上一个元素的边框,然后添加边框,然后将边框添加到新元素中。

我们将要做的是创建一个具有边框样式的CSS类,并根据需要从元素中添加或删除该类。

/* Clicked Date */
.win-btn-selected {
  border: 3px solid red;
}

如果选择了有效日期以外的其他任何日期,则有效日期的背景会一直扩展到边框(如其通常的行为)。因此我们也将为此开设一门课; win-btn-active-unselected这将更改background-originborder-box

最终代码

CSS

@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100&display=swap");

* {
  box-sizing: border-box !important;
  color: white;
  text-transform: capitalize !important;
  font-family: "Noto Sans JP", sans-serif;
  letter-spacing: 2px;
}

body {
  background-color: black;
  display: flex;
  flex-flow: column wrap;
  justify-content: center;
  align-items: center;
}

.win-grid {
  border: 1px solid white;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-gap: 0.2rem;
  align-items: stretch;
  text-align: center;
  padding: 2rem;
  cursor: default;
}

.win-btn {
  padding: 1rem;
  text-align: center;
  border-radius: 0px;
  border: 3px solid transparent;
  background-origin: content-box;
}

/* Today's Date */
.win-btn-active {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 0.2rem;
    border: 3px solid red;
  background: center linear-gradient(red, red) no-repeat;
    background-origin: content-box;
}

/* Today's Date when some other date is clicked*/
.win-btn-active-unselected {
    background-origin: border-box;
}

/* Other Month's Date */
.win-btn-inactive {
  color: #ffffff5f;
}

/* Clicked Date */
.win-btn-selected {
  border: 3px solid red;
}

.win-btn:hover {
  border: 3px solid rgba(255, 255, 255, 0.4);
}

.win-btn-active:hover {
  border: 3px solid hsl(0, 90%, 75%);
}

.win-btn-selected:hover {
  border: 3px solid hsl(0, 70%, 50%) !important;
}

button:focus {
  outline: none;
}

JS

const offset = 69;
const borderWidth = 3;
const angles = []; //in rad
for (let i = 0; i <= 2; i += 0.25) {
  angles.push(Math.PI * i);
}
let nearBy = [];
let activeBtn = document.querySelector(".win-btn-active");
let lastClicked = null;

document.querySelectorAll(".win-btn").forEach((btn) => {
  btn.onclick = (e) => {
    //clear effects from last clicked date and set lastClicked to current item
    if (lastClicked) {
      lastClicked.classList.remove("win-btn-selected");
    }
    lastClicked = e.currentTarget;

    activeBtn.classList.toggle(
      "win-btn-active-unselected",
      e.currentTarget.id !== activeBtn.id
    );
    e.currentTarget.classList.add("win-btn-selected");
  };
});

function clearNearBy() {
  nearBy.splice(0).forEach((e) => (e.style.borderImage = null));
}

const body = document.querySelector(".win-grid");

body.addEventListener("mousemove", (e) => {
  let x = e.clientX; //x position of cursor.
  let y = e.clientY; //y position of cursor

  clearNearBy();

  nearBy = angles.reduce((acc, rad, index, arr) => {
    const offsets = [offset * 0.35, offset * 1.105];

    const elements = offsets.reduce((elementAccumulator, o, i, offsetArray) => {
      if (index % 2 === 0 && i === 0) return elementAccumulator;
      const cx = Math.floor(x + Math.cos(rad) * o);
      const cy = Math.floor(y + Math.sin(rad) * o);
      const element = document.elementFromPoint(cx, cy);
      // console.log("element at", x, y, cx, cy, offsets, (rad * 180) / Math.PI);
      if (
        element &&
        element.classList.contains("win-btn") &&
        !element.classList.contains("win-btn-active") &&
        !element.classList.contains("win-btn-selected") &&
        elementAccumulator.findIndex((ae) => ae.id === element.id) < 0
      ) {
        const brect = element.getBoundingClientRect();
        const bx = x - brect.left; //x position within the element.
        const by = y - brect.top; //y position within the element.
        const gr = Math.floor(offset * 1.7);
        if (!element.style.borderImage)
          element.style.borderImage = `radial-gradient(${gr}px ${gr}px at ${bx}px ${by}px ,rgba(255,255,255,0.3),rgba(255,255,255,0.1),transparent ) 9 / ${borderWidth}px / 0px stretch `;
        console.log("element at", offsets, (rad * 180) / Math.PI, element);

        return [...elementAccumulator, element];
      }
      return elementAccumulator;
    }, []);

    return acc.concat(elements);
  }, []);
});

body.onmouseleave = (e) => {
  clearNearBy();
};

快速代码说明

1.我没有直接将角度转换为弧度,而是直接计算以弧度为单位的角度(0,π/4,π/2、3π/ 4 ... 2π)。

2.win-btn的事件处理程序负责当前选择的元素。 我在这里所做的一个小更改是,我使用classListproperty添加和删除类,而不是使用thestyle`属性来手动更改CSS样式,因为我们要更改的属性具有静态值,这与在光标处具有径向渐变的border-image不同位置。

classList.toggle()

classList.toggle()如果第二个参数的值为false,则该方法将从元素中删除该类,否则将该类添加到该元素中。

3.由于我们以给定的角度检查offset直线上2个点(上图中的绿线)中的元素,因此我将偏移值存储到名为的数组中offsets。 我这样做是为了我们可以遍历2个值并检查每个值处的元素。这样,我们可以扩展该方法以计算2个以上的点,并针对特定角度值检测更多的元素。在这种情况下,可以使用2个偏移值。 因此,该offsets.reduce()方法仅返回这2个元素。我已经在offsets.reduce()方法内部转移了元素选择和样式代码,只是为了避免仅出于样式化目的而对元素进行另一次迭代。 如果没有特定角度的元素,则该elements数组将为空。

==注意==:我使用的偏移值几乎具有原始效果,并且是基于反复试验的结果。您可以使用这些值来获得自己喜欢的外观。

最后,只需将添加elements到累加器中并返回即可。

谢谢! 至此,我们结束了本文的“Win10日历悬停效果”

代码下载

关注公众号免费下载

关注微信公众号【啦啦啦好想biu点什么】回复【Win10日历悬停效果】免费获取

更多相关

gitee.com/haiyongcsdn…

可以的话给个星星吧

最后,不要忘了或支持一下哦

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1

联系我们