当前位置:首页 > 科技  > 软件

Vue3 实现最近很火的酷炫功能:卡片悬浮发光

来源: 责编: 时间:2024-05-22 17:14:11 271观看
导读有趣的动画效果前几天在网上看到了一个很有趣的动画效果,如下,光会跟随鼠标在卡片上进行移动,并且卡片会有视差的效果。那么在 Vue3 中应该如何去实现这个效果呢?图片基本实现思路其实实现思路很简单,无非就是分几步:首先,卡

有趣的动画效果

前几天在网上看到了一个很有趣的动画效果,如下,光会跟随鼠标在卡片上进行移动,并且卡片会有视差的效果。WKO28资讯网——每日最新资讯28at.com

那么在 Vue3 中应该如何去实现这个效果呢?WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

基本实现思路

其实实现思路很简单,无非就是分几步:WKO28资讯网——每日最新资讯28at.com

  • 首先,卡片是相对定位,光是绝对定位
  • 监听卡片的鼠标移入事件mouseenter,当鼠标进入时显示光
  • 监听卡片的鼠标移动事件mouseover,鼠标移动时修改光的left、top,让光跟随鼠标移动
  • 监听卡片的鼠标移出事件mouseleave,鼠标移出时,隐藏光

我们先在 Index.vue 中准备一个卡片页面,光的CSS效果可以使用filter: blur() 来实现。WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

可以看到现在的效果是这样:WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

实现光源跟随鼠标

在实现之前我们需要注意几点:WKO28资讯网——每日最新资讯28at.com

1、鼠标移入时需要设置卡片 overflow: hidden,否则光会溢出,而鼠标移出时记得还原。WKO28资讯网——每日最新资讯28at.com

2、获取鼠标坐标时需要用clientX/Y而不是pageX/Y,因为前者会把页面滚动距离也算进去,比较严谨。WKO28资讯网——每日最新资讯28at.com

刚刚说到实现思路时我们说到了mouseenter、mousemove、mouseleave,其实mouseenter、mouseleave 这二者的逻辑比较简单,重点是 mouseover 这个监听函数。WKO28资讯网——每日最新资讯28at.com

而在 mouseover 这个函数中,最重要的逻辑就是:光怎么跟随鼠标移动呢?WKO28资讯网——每日最新资讯28at.com

或者也可以这么说:怎么计算光相对于卡片盒子的 left 和 top。WKO28资讯网——每日最新资讯28at.com

对此我专门画了一张图,相信大家一看就懂怎么算了。WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

  • left = clientX - x - width/2
  • height = clientY - y - height/2

知道了怎么计算,那么逻辑的实现也很明了了~封装一个use-light-card.ts:WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

接着在页面中去使用:WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

这样就能实现基本的效果啦~WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

卡片视差效果

卡片的视差效果需要用到样式中 transform 样式,主要是配置四个东西:WKO28资讯网——每日最新资讯28at.com

  • perspective:定义元素在 3D 变换时的透视效果
  • rotateX:X 轴旋转角度
  • rotateY:Y 轴旋转角度
  • scale3d:X/Y/Z 轴上的缩放比例

图片图片WKO28资讯网——每日最新资讯28at.com

现在就有了卡片视差的效果啦~WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

给所有卡片添加光源

上面只是给一个卡片增加光源,接下来可以给每一个卡片都增加光源啦!!!WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

让光源变成可配置

上面的代码,总感觉这个 hooks 耦合度太高不太通用,所以我们可以让光源变成可配置化,这样每个卡片就可以展示不同大小、颜色的光源了~像下面一样。WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

既然是配置化,那我们希望是这么去使用 hooks 的,我们并不需要自己在页面中去写光源的dom节点,也不需要自己去写光源的样式,而是通过配置传入 hooks 中。WKO28资讯网——每日最新资讯28at.com

图片WKO28资讯网——每日最新资讯28at.com

所以 hooks 内部要自己通过操作 DOM 的方式,去添加、删除光源,可以使用createElement、appendChild、removeChild 去做这些事~。WKO28资讯网——每日最新资讯28at.com

图片图片WKO28资讯网——每日最新资讯28at.com

完整源码

<!-- Index.vue --><template>  <div class="container">    <!-- 方块盒子 -->    <div class="item" ref="cardRef1"></div>    <!-- 方块盒子 -->    <div class="item" ref="cardRef2"></div>    <!-- 方块盒子 -->    <div class="item" ref="cardRef3"></div>  </div></template><script setup lang="ts">import { useLightCard } from './use-light-card';const { cardRef: cardRef1 } = useLightCard();const { cardRef: cardRef2 } = useLightCard({  light: {    color: '#ffffff',    width: 100,  },});const { cardRef: cardRef3 } = useLightCard({  light: {    color: 'yellow',  },});</script><style scoped lang="less">.container {  background: black;  width: 100%;  height: 100%;  padding: 200px;  display: flex;  justify-content: space-between;  .item {    position: relative;    width: 125px;    height: 125px;    background: #1c1c1f;    border: 1px solid rgba(255, 255, 255, 0.1);  }}</style>
// use-light-card.tsimport { onMounted, onUnmounted, ref } from 'vue';interface IOptions {  light?: {    width?: number; // 宽    height?: number; // 高    color?: string; // 颜色    blur?: number; // filter: blur()  };}export const useLightCard = (option: IOptions = {}) => {  // 获取卡片的dom节点  const cardRef = ref<HTMLDivElement | null>(null);  let cardOverflow = '';  // 光的dom节点  const lightRef = ref<HTMLDivElement>(document.createElement('div'));  // 设置光源的样式  const setLightStyle = () => {    const { width = 60, height = 60, color = '#ff4132', blur = 40 } = option.light ?? {};    const lightDom = lightRef.value;    lightDom.style.position = 'absolute';    lightDom.style.width = `${width}px`;    lightDom.style.height = `${height}px`;    lightDom.style.background = color;    lightDom.style.filter = `blur(${blur}px)`;  };  // 设置卡片的 overflow 为 hidden  const setCardOverflowHidden = () => {    const cardDom = cardRef.value;    if (cardDom) {      cardOverflow = cardDom.style.overflow;      cardDom.style.overflow = 'hidden';    }  };  // 还原卡片的 overflow  const restoreCardOverflow = () => {    const cardDom = cardRef.value;    if (cardDom) {      cardDom.style.overflow = cardOverflow;    }  };  // 往卡片添加光源  const addLight = () => {    const cardDom = cardRef.value;    if (cardDom) {      cardDom.appendChild(lightRef.value);    }  };  // 删除光源  const removeLight = () => {    const cardDom = cardRef.value;    if (cardDom) {      cardDom.removeChild(lightRef.value);    }  };  // 监听卡片的鼠标移入  const onMouseEnter = () => {    // 添加光源    addLight();    setCardOverflowHidden();  };  // use-light-card.ts  // 监听卡片的鼠标移动  const onMouseMove = (e: MouseEvent) => {    // 获取鼠标的坐标    const { clientX, clientY } = e;    // 让光跟随鼠标    const cardDom = cardRef.value;    const lightDom = lightRef.value;    if (cardDom) {      // 获取卡片相对于窗口的x和y坐标      const { x, y } = cardDom.getBoundingClientRect();      // 获取光的宽高      const { width, height } = lightDom.getBoundingClientRect();      lightDom.style.left = `${clientX - x - width / 2}px`;      lightDom.style.top = `${clientY - y - height / 2}px`;      //   设置动画效果      const maxXRotation = 10; // X 轴旋转角度      const maxYRotation = 10; // Y 轴旋转角度      const rangeX = 200 / 2; // X 轴旋转的范围      const rangeY = 200 / 2; // Y 轴旋转的范围      const rotateX = ((clientX - x - rangeY) / rangeY) * maxXRotation; // 根据鼠标在 Y 轴上的位置计算绕 X 轴的旋转角度      const rotateY = -1 * ((clientY - y - rangeX) / rangeX) * maxYRotation; // 根据鼠标在 X 轴上的位置计算绕 Y 轴的旋转角度      cardDom.style.transform = `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`; //设置 3D 透视    }  };  // 监听卡片鼠标移出  const onMouseLeave = () => {    // 鼠标离开移出光源    removeLight();    restoreCardOverflow();  };  onMounted(() => {    // 设置光源样式    setLightStyle();    // 绑定事件    cardRef.value?.addEventListener('mouseenter', onMouseEnter);    cardRef.value?.addEventListener('mousemove', onMouseMove);    cardRef.value?.addEventListener('mouseleave', onMouseLeave);  });  onUnmounted(() => {    // 解绑事件    cardRef.value?.removeEventListener('mouseenter', onMouseEnter);    cardRef.value?.removeEventListener('mousemove', onMouseMove);    cardRef.value?.removeEventListener('mouseleave', onMouseLeave);  });  return {    cardRef,  };};

WKO28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-90040-0.htmlVue3 实现最近很火的酷炫功能:卡片悬浮发光

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Python 并发编程的 12 个实用技巧

下一篇: ViewRootImpl如何负责管理绘制视图树和刷新界面

标签:
  • 热门焦点
  • K60 Pro官方停产 第三方瞬间涨价

    虽然没有官方宣布,但Redmi的一些高管也已经透露了,Redmi K60 Pro已经停产且不会补货,这一切都是为了即将到来的K60 Ultra铺路,属于厂家的正常操作。但有意思的是该机在停产之后
  • Redmi Buds 4开箱简评:才199还有降噪 可以无脑入

    在上个月举办的Redmi Note11T Pro系列新机发布会上,除了两款手机新品之外,Redmi还带来了两款TWS真无线蓝牙耳机产品,Redmi Buds 4和Redmi Buds 4 Pro,此前我们在Redmi Note11T
  • 女孩租房开2小时空调用完100元电费引热议:5级能耗惹不起 月薪过万电费也交不起

    近日,江苏苏州一女孩租房当天充值了100元电费,开着空调不到2小时发现电费已用完。对于为什么这个快,房东表示,电表坏了这种情况很多,之前也遇到过,给租客换
  • K6:面向开发人员的现代负载测试工具

    K6 是一个开源负载测试工具,可以轻松编写、运行和分析性能测试。它建立在 Go 和 JavaScript 之上,它被设计为功能强大、可扩展且易于使用。k6 可用于测试各种应用程序,包括 Web
  • 使用LLM插件从命令行访问Llama 2

    最近的一个大新闻是Meta AI推出了新的开源授权的大型语言模型Llama 2。这是一项非常重要的进展:Llama 2可免费用于研究和商业用途。(几小时前,swyy发现它已从LLaMA 2更名为Lla
  • 一文掌握 Golang 模糊测试(Fuzz Testing)

    模糊测试(Fuzz Testing)模糊测试(Fuzz Testing)是通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。可以用来发现应用程序、操作系统和网络协议等中的漏洞或
  • 每天一道面试题-CPU伪共享

    前言:了不起:又到了每天一到面试题的时候了!学弟,最近学习的怎么样啊 了不起学弟:最近学习的还不错,每天都在学习,每天都在进步! 了不起:那你最近学习的什么呢? 了不起学弟:最近在学习C
  • iQOO 11S屏幕细节公布:首发三星2K E6全感屏 安卓最好的直屏手机

    日前iQOO手机官方宣布,新一代电竞旗舰iQOO 11S将会在7月4日19:00正式与大家见面。随着发布时间的日益临近,官方关于该机的预热也更加密集,截至目前已
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
Top