《fun-widgets:简单却有趣的触点交互(TouchCard)》

Hi guys, 最近在用豆包的时候发现一个有意思的交互应用,下面我们一起来看下怎么实现

样例

为了更好得展示最终效果,直接使用豆包的界面作为样例

UI上可以看出:

  • 光标进入时PersonInfo模块会出现"下陷"效果
  • "下陷"效果延后触发
  • 右侧Memojis"悬浮"效果

分解开始

一、"下陷"效果复刻

通过肉眼观看,当光标移入时,整个卡片会出现下陷,这是一种视觉欺骗。

我们可以通过mousemove和csstransform的rotateX&rotateY属性可以达到此效果。 逻辑通了,那就开始吧。

  • 使用vue&tailwindcss快速搭建一个组件。给予属性@mousemove函数handleMouseMove
HTML 复制代码
<template>
  <div
    class="w-screen h-screen flex justify-center items-center bg-neutral-200"
  >
    <div
        id="card"
      class="w-1/2 h-60 relative z-1 flex items-center"
    >
      <div
        ref="touchCardRef"
        class="w-full h-full backdrop-blur-[.58594rem] rounded-2xl border-1 border-white shadow-lg shadow-gray-400 flex flex-col justify-center card"
        @mousemove="handleMouseMove"
      >
        <div class="ml-20 text-4xl text-[#141413] font-bold">@小王饿了</div>
        <div class="ml-20 mt-6 text-xl text-sky-500 font-bold">
          #fun-widgets/TouchCard
        </div>
      </div>
    </div>
  </div>
</template>
<style lang="scss" scoped>
.card {
  backdrop-filter: blur(0.58594rem);
  background: linear-gradient(
    113deg,
    hsla(0, 0%, 100%, 0.72) 10.29%,
    hsla(0, 0%, 100%, 0) 100.35%
  );
}
</style>
  • handleMouseMoveFn
    @mousemove会返回一个MouseEvent类型(点击查看内容)。此类型有诸多关于光标在浏览器内坐标的参数,我们取光标在touchCardRef内的参数offsetX&offsetY。 使用@vueuse/core/useElementSize函数获取touchCardRef节点的宽高,得到一个
    {x:[-width/2, width/2],y:[-height/2, height/2]}的坐标体
    由此可以推断:

    • 当光标所处区域为正y时,卡片向上倾斜,rotateX为正角,反之rotateX为负角

    • 当光标所处区域为正x时,卡片向右倾斜,rotateY为负角,反之rotateY为正角

    rotateX和rotateY属性可以控制元素倾角

注意1: 最终为3d效果,添加transform-3d属性让transform支持此效果。

注意2: 在操控倾斜时倾斜会被视距所影响,perspective属性可以替我们解决这个问题。将视距设置为2000px,避免畸变。

CSS 属性 perspective 指定了观察者与 z=0 平面的距离,使具有三维位置变换的元素产生透视效果。z>0 的三维元素比正常大,而 z<0 时则比正常小,大小程度由该属性的值决定。

观察原型发现倾斜极值为-6,6。接下来就是一道简单的数学题:x与width如何得到-6,6区间值?

逻辑很简单:

  1. 以w/2为原点
  2. x − w / 2 x-w/2 x−w/2得到所处正负极
  3. ( x − w / 2 ) / ( w / 2 ) (x-w/2)/(w/2) (x−w/2)/(w/2)得到所处坐标比例
  4. ( x − w / 2 ) / ( w / 2 ) ∗ 6 (x-w/2)/(w/2)*6 (x−w/2)/(w/2)∗6得到-6,6区间
  5. 简化下公式就是 ( ( x / w / 2 ) − 1 ) ∗ 6 ((x/w/2)-1)*6 ((x/w/2)−1)∗6
js 复制代码
const touchTransform = (x: number, y: number): void => {
  const rotateX = -(y / (height.value / 2) - 1) * 6;
  const rotateY = (x / (width.value / 2) - 1) * 6;
  transform.value = `perspective(2000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
};

到此,"下陷"效果的最后一块拼图完成了。看下整体代码:

js 复制代码
<script lang="ts" setup>
import { ref } from "vue";
import { useElementSize } from "@vueuse/core";

const transform = ref<string>("");
const touchCardRef = ref<HTMLElement>();
const { width, height } = useElementSize(touchCardRef);

const touchTransform = (x: number, y: number): void => {
  const rotateX = -(y / (height.value / 2) - 1) * 10;
  const rotateY = (x / (width.value / 2) - 1) * 10;
  transform.value = `perspective(2000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
};

const handleMouseMove = (event: MouseEvent) => {
  const { offsetX: x, offsetX: y } = event;
  touchTransform(x, y);
};
</script>
<template>
  <div
    class="w-screen h-screen flex justify-center items-center bg-neutral-200"
  >
    <div
      class="w-1/2 h-60 relative z-1 flex items-center transform-3d"
      :style="{ transform }"
    >
      <div
        ref="touchCardRef"
        class="w-full h-full backdrop-blur-[.58594rem] rounded-2xl border-1 border-white shadow-lg shadow-gray-400 flex flex-col justify-center card"
        @mousemove="handleMouseMove"
      >
        <div class="ml-20 text-4xl text-[#141413] font-bold">@小王饿了</div>
        <div class="ml-20 mt-6 text-xl text-sky-500 font-bold">
          #fun-widgets/TouchCard
        </div>
      </div>
    </div>
  </div>
</template>
<style lang="scss" scoped>
.card {
  backdrop-filter: blur(0.58594rem);
  background: linear-gradient(
    113deg,
    hsla(0, 0%, 100%, 0.72) 10.29%,
    hsla(0, 0%, 100%, 0) 100.35%
  );
}
</style>

二、"下陷"效果延后触发

接着处理"下陷"效果的延后触发,这块很简单,添加transition属性就行。

css 复制代码
transition: transform 0.15s linear;

三、右侧Memojis"悬浮"效果

在translate中有3个属性可以让节点偏移

  • transform: translateX(2em)
  • transform: translateY(3in)
  • transform: translateZ(2px)

想要实现浮于上层的效果,这里选择translateZ。借鉴写tailwind的图片,设置对应的参数可以让图片悬于上方。

  • 增加图片,添加translate-z-12属性,进化至完全体 。(BGM:Butterfly)
js 复制代码
<template>
  <div
    class="w-screen h-screen flex justify-center items-center bg-neutral-200"
  >
    <div
      class="w-1/2 h-60 relative z-1 transform-3d transition-transform flex items-center"
      :style="{ transform }"
    >
      <div
        ref="touchCardRef"
        class="w-full h-full backdrop-blur-[.58594rem] rounded-2xl border-1 border-white shadow-lg shadow-gray-400 flex flex-col justify-center card"
        @mousemove="handleMouseMove"
        @mouseout="handleMouseOut"
      >
        <div class="ml-20 text-4xl text-[#141413] font-bold">@小王饿了</div>
        <div class="ml-20 mt-6 text-xl text-sky-500 font-bold">
          #fun-widgets/TouchCard
        </div>
      </div>
      <img
        class="absolute pointer-events-none translate-z-12 w-1/4 -right-1/8 rounded-full border-1 border-white"
        src="https://p3-flow-imagex-sign.byteimg.com/user-avatar/f9e7748c4fea045de8a774d0836ad177~tplv-a9rns2rl98-image.jpeg?rk3s=98c978ad&x-expires=1743502443&x-signature=YLBy9quP5%2B3%2BsPEza3K4SfeRfNg%3D"
      />
    </div>
  </div>
</template>

题外话

  1. 欢迎分享更多有趣交互
  2. 动图制作by:kapwing
  3. 源码已传至Github:github.com/WZJvearyfun...

我是小王饿了 。一个前端开发,曾任职500强车企前端负责人、新零售互联网、事业单位。

好捣鼓点东西吃,有点手艺。爱看小说,爱旅游,人生不受限。

29岁的人和19岁的人其实没差别,除了体重...

相关推荐
lpfasd1234 小时前
node-sass 停止维护!老项目迁移 Dart Sass 完整教程
前端·rust·sass
Data analyse4566 小时前
rnweb 回放数据量太大怎么办?分片存储与断点续传的实现思路
前端·网络·算法·数据分析
xiaogai_gai6 小时前
金蝶收料通知单查询策略实战教程:从源系统拉取到中间层落地的完整路径
java·前端·数据库
环境栈笔记7 小时前
指纹浏览器 WebRTC 怎么设置?从 ICE 候选、代理出口到功能验证
前端·人工智能·后端·自动化
赵大仁8 小时前
官网性能优化实战:LCP 3.2s → 2.1s、CLS 0.107 → 0,以及被我推翻的 6 个判断
前端·性能优化·cls·lcp·core web vitals
xiancai_xianyu8 小时前
Text2SQL落地生产库前,SQL要过哪三道闸
前端·数据库·sql·sql注入·text2sql·ai agent开发·企业级ai框架
小羊没烦恼!8 小时前
T-SQL查询进阶--理解SQL SERVER中的分区表
服务器·开发语言·前端·javascript·html
史瓦辛格8 小时前
给 DeepSeek Harness 写费用插件:把「这次会话花了多少钱」算准
前端·deepseek
星光不问赶路人8 小时前
Vue 3 + TypeScript 中 enum 的使用实践
前端·vue.js
鸥梨菌Honevid8 小时前
UVM 中 REQ 和 RSP 类型详解:从通信模型到 item_done 与 put_response
java·前端·servlet