整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

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

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

html表单:HTML表单用于收集用户输入的数据,并将数据发送到服务器进行处理,创建了一个包含多种表单元素的表单。其中,<label> 标签用来描述表单元素,<input> 标签用来创建表单输入框,<textarea> 标签用来创建多行文本输入框,<button> 标签用来创建提交按钮等等。下面我们一起来学习了解吧



表单标签

HTML提供了几个用于创建表单的标签,包括<form>、<input>、<label>、<select>、<option>、<textarea>和<button>。

<form>标记用于创建表单元素,该元素可以包含一个或多个表单控件,如文本输入、复选框、单选按钮等。

action属性指定表单数据应提交到的URL,method属性指定用于提交的HTTP方法(通常为“GET”或“POST”)。

下面是一个带有文本输入和提交按钮的简单表单示例:

<form action="submit-form.php" method="POST">
  <label for="name">Name:</label>
  <input type="text" id="name" name="name">
  <button type="submit">Submit</button>
</form>

在本例中,<label>标记用于使用for属性和id属性将文本输入与其标签相关联。输入的name属性指定将与表单数据一起提交的表单字段的名称。

<select>标记用于创建选项的下拉列表,<option>标记用于定义每个选项。下面是一个例子:

<label for="color">Favorite color:</label>
<select id="color" name="color">
  <option value="red">Red</option>
  <option value="green">Green</option>
  <option value="blue">Blue</option>
</select>

在本例中,每个选项的value属性指定将与表单数据一起提交的值。

<textarea>标记用于创建多行文本输入。下面是一个例子:

<label for="message">Message:</label>
<textarea id="message" name="message"></textarea>

<button>标记用于创建一个按钮,该按钮可用于提交表单或执行其他操作。 下面是一个例子:

<button type="submit">Submit</button>

文本框、密码框和多行文本框

HTML中,文本框、密码框和多行文本框分别使用input元素的不同type属性值来实现。

  • 文本框:使用<input type="text">标签实现;
  • 密码框:使用<input type="password">标签实现;
  • 多行文本框:使用<textarea></textarea>标签实现。

文本框、密码框和多行文本框是用来接收用户输入的常见表单元素。它们分别使用不同的标签和属性来实现。

  1. 文本框

文本框(text box)是用于接受单行文本输入的表单元素。可以通过设置<input>标签的type属性为"text"来创建文本框。例如:

<label for="username">用户名:</label>
<input type="text" id="username" name="username">

上面的示例代码中,id属性定义了文本框的唯一标识符,name属性定义了提交表单时该文本框的名称。

  1. 密码框

密码框(password field)是用于接受密码等敏感信息的表单元素,并且会将用户输入的字符隐藏起来。可以通过设置<input>标签的type属性为"password"来创建密码框。例如:

<label for="password">密码:</label>
<input type="password" id="password" name="password">
  1. 多行文本框

多行文本框(text area)可用于接收包含多个行的文本输入,例如评论或留言等。可以通过使用<textarea>标签来创建多行文本框,然后使用它的rows和cols属性来指定它的尺寸。例如:

<label for="message">留言:</label>
<textarea id="message" name="message" rows="5" cols="30"></textarea>

上面的示例代码中,rows属性定义了多行文本框的行数,cols属性定义了多行文本框每一行的字符数。

单选框和复选框

HTML中,单选框和复选框是常见的表单元素,用于让用户选择不同选项。它们分别使用<input>标签的不同type属性值来实现。

  1. 单选框

单选框(radio button)允许用户在一组选项中选择一个。可以使用相同的name属性来将多个单选框关联起来。当用户勾选其中一个单选框时,其他单选框将自动取消选中状态。可以通过设置<input>标签的type属性为"radio"来创建单选框。例如:

<form>
  <p>请选择性别:</p>
  <label><input type="radio" name="gender" value="M">男士</label>
  <label><input type="radio" name="gender" value="F">女士</label>
  <label><input type="radio" name="gender" value="O">其他</label>
</form>

上面的示例代码中,三个单选框使用相同的name属性,以便将它们关联起来。当用户勾选其中一个单选框时,另外两个单选框将自动取消选中状态。

  1. 复选框

复选框(checkbox)允许用户选择多个选项。可以使用相同的name属性来将多个复选框关联起来。当用户勾选其中一个复选框时,其他复选框保持不变。可以通过设置<input>标签的type属性为"checkbox"来创建复选框。例如:

<form>
  <p>请选择语言:</p>
  <label><input type="checkbox" name="lang" value="js">JavaScript</label>
  <label><input type="checkbox" name="lang" value="py">Python</label>
  <label><input type="checkbox" name="lang" value="java">Java</label>
</form>

上面的示例代码中,三个复选框使用相同的name属性,以便将它们关联起来。用户可以勾选多个复选框。

单选框和复选框的值可以通过value属性进行指定,当表单提交时,浏览器会将被选中的单选框或复选框的值作为表单数据提交到服务器并进行处理。

下拉列表框和列表框

HTML中,下拉列表框和列表框是用来显示多个选项的表单元素。它们分别使用不同的标签和属性来实现。

  1. 下拉列表框

下拉列表框(dropdown list)可以让用户从多个选项中选择一个选项。可以使用<select>标签来创建下拉列表框,然后使用<option>标签定义每一个选项。例如:

<form>
  <p>请选择城市:</p>
  <select name="city">
    <option value="bj">北京</option>
    <option value="sh">上海</option>
    <option value="gz">广州</option>
    <option value="sz">深圳</option>
  </select>
</form>

上面的示例代码中,四个<option>标签定义了四个选项。其中"value"属性指定每个选项的值,"text"节点指定每个选项的文本内容。当用户点击下拉框时,会出现一个下拉菜单列出所有可用选项,用户可以从中选择一个选项。

  1. 列表框

列表框(list box)是另一种显示多个选项的表单元素,与下拉列表框不同的是,列表框可以同时显示多个选项。可以使用<select>标签来创建列表框,然后使用<option>标签定义每一个选项并设置multiple属性为"multiple"来启用多选模式。例如:

<form>
  <p>请选择兴趣爱好:</p>
  <select name="interests" multiple>
    <option value="reading">阅读</option>
    <option value="music">音乐</option>
    <option value="painting">绘画</option>
    <option value="sports">运动</option>
  </select>
</form>

上面的示例代码中,multiple属性指定该列表框启用多选模式。当用户选择一个或多个选项时,这些选项将被高亮显示。

总之,在HTML中可以使用下拉列表框和列表框来为用户提供多个选项让用户选择,并在表单提交时将选择的结果作为表单数据提交到服务器。

按钮和图像按钮

HTML中,按钮和图像按钮是用来触发事件或提交表单的表单元素。它们分别使用<button>标签和<input>标签的不同type属性值来实现。

  1. 普通按钮

普通按钮(button)通常用于触发一些事件,例如刷新页面、清空表单等。可以使用<button>标签来创建普通按钮。例如:

<button>点击我</button>

上面的示例代码中,创建了一个简单的按钮,点击按钮会触发一些事件。

  1. 提交按钮

提交按钮(submit button)用于将表单数据提交到服务器进行处理。可以使用<input>标签,并将其type属性设置为"submit"来创建提交按钮,例如:

<input type="submit" value="提交">

上面的示例代码中,设置了一个提交按钮,当用户点击该按钮时,浏览器会将表单数据提交到服务器并进行处理。

  1. 图像按钮

图像按钮(image button)与普通按钮类似,但它使用图片而不是文本作为按钮的标识。可以使用<input>标签,并将其type属性设置为"image"来创建图像按钮,例如:

<input type="image" src="submit.png" alt="提交">

上面的示例代码中,创建了一个带有图片的图像按钮。当用户单击图像按钮时,浏览器会将表单数据提交到服务器并进行处理。

总结

以上就是今天要讲的内容,本文简单介绍了HTML的表单的知识点,更多内容可以查看THTML官方文档

如果觉得有用,欢迎明天再来,继续合集的下一篇文章学习
遇到问题,私信我!!~~

次讲Qt Style Sheet(QSS),QSS是一种与CSS类似的语言,实际上这两者几乎完全一样。既然谈到CSS我们就有必要说一下盒模型。

1、盒模型(The Box Model)

在样式中,每一个UI控件都可以抽象成一个盒模型:

盒模型由4个同心矩形组成,从外到内分别是外边距边界内衬内容,大部分UI视觉内容都包含在内容矩形中。默认情况下,前三个矩形的大小属性都是0,因此这四个矩形都重叠成一个了。这里与前三个大小相关的属性有:

  • margin:代指外边距矩形与边界矩形之间的间距。
  • border-width:代指外边距矩形与内衬矩形之间的间距。
  • padding:代指内衬矩形与内容矩形之间的间距。

2、QSS(Qt Style Sheet)

QSS与CSS相似,因此这里只大概介绍QSS的特点:

  • 子控件:Qt允许我们控制复杂控件的子控件,例如QComboBox的下拉按钮、QCheckBox的勾选框等等。
  • 伪状态:Qt包含一些特殊的状态:last、first等。
  • 支持自定义类的样式设置:得力于Qt的元对象系统,QSS可以对用户自己定义的类进行样式设置。
  • 命名空间特殊语法:由于C++中的命名控件写法(::)与Q 的子控件写法冲突,因此改用--来表示命名空间。
  • Qt属性支持:任何通过Q_PROPERTY定义的属性和Q_ENUM枚举类都可以被样式化。

3、自定义样式实例

这里我们通过一个示例程序来接触QSS,因为实例程序涉及多个控件但我们篇幅有限,我们只介绍菜单相关的样式设置。菜单分为两部分:菜单栏(QMenuBar)、菜单(QMenu)。

3.1、菜单栏样式

我们把菜单栏宽度设置成大于等于菜单item的宽度60px(这里的width不指代整个菜单栏的宽度,比较奇怪),防止item的宽度被菜单栏的宽度限制,并且设置灰色点边框。

QT开发交流+赀料君羊:714620761

QMenuBar {
        width:60px;
        background-color:white;
        border:1px dotted gray;

}

item这里代指菜单栏上的每一项菜单,我们设置边框位groove以使其看起来有凹凸效果;设置margin-right,这样两个item之间会有合适的间隔;我们还在不同的状态下设置不同的背景颜色,这样交互上比较友好。

QMenuBar::item {
    height:15px;
    width:60px;

    background:transparent;
    border:2px groove gray;
    margin-right:1px;
}
QMenuBar::item:selected {
    color:green;
    background-color:rgb(236, 242, 245);
}
QMenuBar::item:pressed {
    color:green;
    background-color:rgb(233, 227, 227);
}

3.2、菜单样式

菜单我们设置成与菜单栏一样;菜单的item指代每一个菜单选项,我们对其设置合适的margin和padding大小,border、item的selected和pressed状态设置成菜单栏的样式,使其看起来布局空间合理且与菜单栏一致:

QMenu {
    background-color:white;
    border:1px solid rgb(0, 171, 255);
    padding:1px;
}
QMenu::item {

    height:15px;
    width:60px;

    background-color:white;
    margin:1px;

    padding:2px 2px 2px 20px;

    border:2px groove gray;
}
QMenu::item:selected {
    color:green;
    background-color:rgb(236, 242, 245);
}
QMenu::item:pressed {
    color:green;
    background-color:rgb(233, 227, 227);
}

我们重绘菜单之间的间隔条(separator),简单的用浅蓝色填充1像素高的背景:

QMenu::separator {
    height:1px;
    margin:1px;
    background-color:lightblue;
}

菜单的选中指示框和子菜单我们用图片来显示,图片选用png格式,保持透明通道,使其与背景一致:

QMenu::indicator:checked {
    width:10px;
    height:10px;
    margin-left:3px;
    border:0px solid blue;
    image:url(:/checked-icon.png);
}
QMenu::right-arrow {
    width:15px;
    height:15px;
    image:url(:/right-arrow.png);
}

4、运行结果

程序的完整运行结果如下:

5、总结

优点:

  • 开发效率高,使用简单。
  • QSS绘制模式统一(盒模型),一次学习多处使用。

缺点:

  • 与CSS一样,某些绘制细节比较复杂,需要尝试。例如QLCDNumber没有文档说明可以应用QSS,但是实际上可以设置border和color,然而hover状态下color属性不管用。
  • 绘制控件相对比较受限制。因为同CSS一样,绘制都是按照既有的规则进行,自然不能像Qt原生控件那样灵活(但是个人觉得很多场景用QSS就够用了)。
  • 某些控件的子控件不能独立绘制。一旦一个子控件设置了样式,其他子控件也需要设置。