百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

如何实现一个超丝滑的勾选框组件?

yuyutoo 2024-10-11 21:37 4 浏览 0 评论

前言

Zag 和 PandaCSS 都是出自 chakra 团队之手,Zag 聚焦于处理组件的逻辑,而 PandaCSS 聚焦于通过 ts 来维护样式,将两者进行搭配会有怎么样的使用体验呢?这篇文章将继续以 vuesax 中 checkbox 组件的样式作为参考,结合 Zag 和 PandaCSS 进行 vue3 版本的重构,实现一个超丝滑的勾选框组件。


使用 Zag 实现核心逻辑

Zag 中将 Checkbox 分为三个组成部分:

  • root:根元素,用于包裹内部元素
  • control: 控制器
  • label: 标签内容

我们首先在 Zag Checkbox 文档中复制 JSX 的实例代码:

import * as checkbox from "@zag-js/checkbox"
import { normalizeProps, useMachine } from "@zag-js/vue"
import { defineComponent, h, Fragment, computed } from "vue"

export default defineComponent({
  name: "Checkbox",
  setup() {
    const [state, send] = useMachine(checkbox.machine({ id: "checkbox" }))

    const apiRef = computed(() =>
      checkbox.connect(state.value, send, normalizeProps),
    )

    return () => {
      const api = apiRef.value
      return (
        <label {...api.rootProps}>
          <span {...api.labelProps}>
            Input is {api.isChecked ? "checked" : "unchecked"}
          </span>
          <div {...api.controlProps} />
          <input {...api.hiddenInputProps} />
        </label>
      )
    }
  },
})

这段代码使用了 useMachine 函数创建了一个状态机,并且写了一个无样式的基础 checkbox 结构:

接下来我们为 checkbox 组件补充样式.

实现打勾动画图标

想要实现丝滑的勾选框效果,最直观的是打勾的动画。这个效果可以通过 SVG 或者纯 css 实现,这里我使用的是 css 来实现的。:

首先我们要想想如何实现一个勾勾的效果,??由一长一短两个斜边组成,那么我们只需要放置一个矩形,设置一定的旋转角度,并设置其中的两个边框,就能实现一个??的形状:

代码实现如下:


import { defineComponent } from "vue";

import { css, cx } from "@/styled-system/css";

const IconCheck = defineComponent({
  props: {
    color: {
      type: String,
      default: css({ colorPalette: "gray" }),
    },
    size: {
      type: String,
      default: css({ colorPalette: "gray" }),
    },
    customCSS: {
      type: String,
    },
  },
  setup(props) {
    return () => (
      <i
        class={cx(
          css({
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
          }),
          props.customCSS,
        )}
      >
        <div
          class={css({
            position: "relative",
            width: "80%",
            height: "40%",
            transform: "rotate(-45deg)",
          })}
        >
          <div
            class={css({
              position: "absolute",
              left: "0",
              width: "full",
              height: "full",
              borderLeft: "2px solid white",
              animation: "checkShort 0.15s",
            })}
          />
          <div
            class={css({
              position: "absolute",
              left: "0",
              width: "full",
              height: "full",
              borderBottom: "2px solid white",
              animation: "checkLong 0.5s",
            })}
          />
        </div>
      </i>
    );
  },
});

export default IconCheck;

上面这段代码中,定义了一个矩形,宽高分别为最外层容器的 80% 和 40%,transform: "rotate(-45deg)", 则设置了矩形的旋转角度,内部放置两个与矩形宽高一致的容器,分别设置 borderLeft 和 borderBottom ,这样就组成了一长一短两条线。

这里之所以需要在内部放两个容器单独设置边框,而不是直接在矩形设置边框,是为了更好的实现动画效果,长短边分别设置了两个持续时间不同的动画 checkShort 0.15s 和 checkLong 0.5s:

checkShort: {
  "0%": {
    height:0,
  },
  "100%":{
    height: "full",
  }
},
checkLong: {
  "0%": {
    width: 0,
  },
  "30%":{
    width: 0,
  },
  "100%": {
    width: "full",
  }
}

短边从最开始就执行动画,持续时间为长边动画的 30%,长边则在 0-30% 时不执行,30% 之后开始执行,这样就能实现短边动画执行完成后,长边动画再执行的效果。

之所以不使用 animation-delay 去延迟执行长边动画,是因为这种方式会导致动画延迟执行前,长边会先展示出来,效果就不对了。如果要使用这种方式还得单独做一些动画延迟前的隐藏处理,会比较麻烦:

实现 Hover 效果

为了让用户更容易感知勾选框是可以交互的,我们为勾选框增加未勾选和关状态的 hover 效果。

未勾选状态

未勾选状态的 hover 效果,默认只有灰色边框,鼠标悬浮后变为灰色背景:

这里有个注意点,我们鼠标悬浮在勾选框的最外层,也可以触发内层的 hover 样式,如果直接使用 hover 效果是没法做到的,这样只能鼠标悬浮在边框内才能触发。

如果我们没有使用任何样式库,实现这个效果可以通过维护一个 鼠标是否 hover 的状态,并通过 onMouseEnter 和 onMouseLeave 来更新这个状态,再在内层根据这个状态动态渲染样式。

但这里我们可以使用 pandaCSS 的 group 选择器来实现。

首先在勾选框最外层元素增加 group 类名:

<label
  {...api.rootProps}
  class={[
    css({
      display: "flex",
      alignItems: "center",
      cursor: "pointer",
    }),
+    "group",
  ]}
>

然后在内层的 control 元素增加基础样式:

<div
    {...api.controlProps}
    class={css({
      width: "24px",
      height: "24px",
      borderRadius: "8px",
      border: api.isChecked
        ? "none"
        : "token(colors.gray.200) solid 2px",
      transition: "all 0.3s",
      marginRight: "4px",
      flexShrink: "0",
      _groupHover: {
        background: "gray.200",
      },
    })}
  >
  // ...
  </div>

这里的 _groupHover 即为 group 选择器,当带有 group 类名的元素触发 hover 时,内部的元素都可以使用 _groupHover 设置特定样式。这样我们就实现了前文图中的效果。

勾选状态

在勾选时,会有一个蓝色色块放大渐出的效果,我们先来实现这个样式。

<Transition
  enterFromClass={css({
    transition: "all 0.2s",
    transform: "scale(0.5)",
    opacity: "0",
  })}
  enterToClass={css({
    transition: "all 0.2s",
    transform: "scale(1)",
    opacity: "1",
  })}
  leaveToClass={css({
    transition: "all 0.2s",
    transform: "scale(0.5)",
    opacity: "0",
  })}
>
  {api.isChecked && (
    <div
      class={cx(
        props.color,
        css({
          width: "full",
          height: "full",
          background: "colorPalette.600",
          borderRadius: "inherit",
          display: "flex",
          alignItems: "center",
          justifyContent: "center",
          transition: "all 0.3s",
        }),
      )}
    >
      <IconCheck
        customCSS={css({
          width: "18px",
          height: "18px",
        })}
      />
    </div>
  )}
</Transition>

这里我们使用 vue 中的 Transition 组件来实现动画效果,通过改变scale 和 opacity 实现元素大小和透明度的过渡动画,内部包裹着勾选的图标。

实现了勾选的效果,继续实现勾选后的 hover 样式。勾选后在 hover 时,勾选框的外层有一个与主题色相同的外层阴影效果:

这里我们依然使用 group 选择器来设置 hover 样式:

<div
  class={cx(
    props.color,
    css({
      width: "full",
      height: "full",
      background: "colorPalette.600",
      borderRadius: "inherit",
      display: "flex",
      alignItems: "center",
      justifyContent: "center",
      transition: "all 0.3s",
+      _groupHover: {
+        boxShadow:
+          "0px 3px 15px 0px color-mix(in srgb, token(colors.colorPalette.600) 40%, transparent)",
+      },
    }),
  )}
>
  <IconCheck
    customCSS={css({
      width: "18px",
      height: "18px",
    })}
  ></IconCheck>
</div>

在阴影效果的代码中 0px 3px 15px 0px color-mix(in srgb, token(colors.colorPalette.600) 40%, transparent) ,前面几个设置阴影大小的参数很容易理解,但是后面阴影颜色的实现大家可能比较陌生,单独解释一下:

  • token(colors.colorPalette.600):token 是 pandaCSS 中提供的一种 token 使用方法,用于在 border,boxSahdow 这种组合多个参数的样式中引用 token 值。相关文档:panda-css.com/docs/themin…
  • color-mix() : color-mix 则是原生 css 的函数,用于接收两个颜色值,并返回在指定色彩模式、以指定比例混合后的颜色。相关文档:developer.mozilla.org/zh-CN/docs/…

我这里用法的含义是在 srgb 的色彩模式下,将主题色(token(colors.colorPalette.600)) 与透明色(transparent),以 40% 的比例进行混合,最终合成的颜色就是 40% 透明度的主题色。 color-mix() 函数是 pandaCSS 中推荐用户用于为内置颜色设置透明度的方法,除此以外并没有发现其他更简便的方式。

完成双向绑定

最后我们完善一下勾选框的双向绑定逻辑逻辑,

  • 通过 Zag 中的 onCheckedChange 配合 emit 可以实现 Zag 内部状态向外传递
  • 通过监听 propmodelValue 的改变,执行 apiRef.value.setChecked 可以实现外部状态向 Zag 内传递

实现的代码如下:

const [state, send] = useMachine(
  checkbox.machine({
    id: useId("checkbox"),
    onCheckedChange(detail) {
      emit("update:modelValue", detail.checked);
    },
  }),
);

const apiRef = computed(() =>
  checkbox.connect(state.value, send, normalizeProps),
);

watch(
  () => props.modelValue,
  () => {
    apiRef.value.setChecked(props.modelValue);
  },
);


? 使用 Vue+Zag+PandaCSS 实现一个超丝滑的勾选框组件
原文链接:https://juejin.cn/post/7295954109404463155

相关推荐

史上最全的浏览器兼容性问题和解决方案

微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...

平面设计基础知识_平面设计基础知识实验收获与总结
平面设计基础知识_平面设计基础知识实验收获与总结

CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...

2025-02-21 16:01 yuyutoo

写作排版简单三步就行-工具篇_作文排版模板

和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...

写一个2048的游戏_2048小游戏功能实现

1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...

今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……

  最近的天气暖和得让很多小伙伴们喊“热”!!!  昨天的气温到底升得有多高呢?你家有没有榜上有名?...

CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式

之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...

柠檬科技肖勃飞:大数据风控助力信用社会建设

...

你的自我界限够强大吗?_你的自我界限够强大吗英文

我的结果:A、该设立新的界限...

行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?

行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...

让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华

去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...

2025-02-21 16:00 yuyutoo

今年国家综合性消防救援队伍计划招录消防员15000名

记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...

一起盘点最新 Chrome v133 的5大主流特性 ?

1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...

竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使

style="text-align:center;"data-mce-style="text-align:...

学物理能做什么?_学物理能做什么 卢昌海

作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...

你不知道的关于这只眯眼兔的6个小秘密
你不知道的关于这只眯眼兔的6个小秘密

在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...

2025-02-21 16:00 yuyutoo

取消回复欢迎 发表评论: