twin.macro实现星级评分样式:从基础到高级交互

twin.macro实现星级评分样式:从基础到高级交互

【免费下载链接】twin.macro 🦹‍♂️ Twin blends the magic of Tailwind with the flexibility of css-in-js (emotion, styled-components, solid-styled-components, stitches and goober) at build time. 【免费下载链接】twin.macro 项目地址: https://gitcode.com/gh_mirrors/tw/twin.macro

在Web开发中,星级评分系统是用户反馈和产品评价的核心组件。传统实现方案往往需要大量CSS代码和JavaScript交互逻辑,而使用twin.macro可以大幅简化这一过程。本文将展示如何利用twin.macro结合Tailwind CSS和CSS-in-JS技术,快速构建美观且交互丰富的星级评分组件。

实现原理与优势

twin.macro是一个将Tailwind CSS的工具类语法与CSS-in-JS解决方案(如emotion、styled-components)结合的构建时工具。通过twin.macro,开发者可以直接在JavaScript/TypeScript代码中使用Tailwind的工具类,同时享受CSS-in-JS的组件封装和作用域隔离优势。

星级评分实现的核心在于:

  • 使用Tailwind的工具类快速构建星星样式
  • 利用CSS掩码(mask)实现星形图标
  • 通过twin.macro的条件样式处理选中状态
  • 结合JavaScript实现交互逻辑

官方文档中关于自定义样式的指南docs/prop-styling-guide.md详细介绍了类似场景的实现方法。

基础实现:静态星级展示

最基础的星级评分组件仅用于展示评分结果,不需要用户交互。以下是使用twin.macro实现的静态星级展示:

import tw from 'twin.macro';

const StaticRating = ({ rating = 4 }) => {
  return (
    <div tw="flex items-center">
      {[1, 2, 3, 4, 5].map((star) => (
        <svg
          key={star}
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 24 24"
          tw={`w-6 h-6 ${
            star <= rating ? 'text-yellow-400 fill-yellow-400' : 'text-gray-300 fill-gray-300'
          }`}
        >
          <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
        </svg>
      ))}
      <span tw="ml-2 text-gray-600">{rating.toFixed(1)}</span>
    </div>
  );
};

export default StaticRating;

这段代码使用了twin.macro的模板字面量语法,根据传入的rating值动态切换星星的颜色。黄色星星表示已评分部分,灰色星星表示未评分部分。

交互式星级评分:基础版本

要实现用户可点击的星级评分,我们需要添加交互逻辑和状态管理。以下是一个基础的交互式星级评分组件:

import { useState } from 'react';
import tw from 'twin.macro';

const InteractiveRating = ({ initialRating = 0, onRatingChange }) => {
  const [rating, setRating] = useState(initialRating);
  const [hoverRating, setHoverRating] = useState(0);
  
  const handleClick = (star) => {
    setRating(star);
    onRatingChange(star);
  };
  
  return (
    <div tw="flex items-center cursor-pointer">
      {[1, 2, 3, 4, 5].map((star) => (
        <svg
          key={star}
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 24 24"
          tw={`w-8 h-8 transition-colors duration-200 ${
            hoverRating >= star 
              ? 'text-yellow-400 fill-yellow-400' 
              : rating >= star
                ? 'text-yellow-400 fill-yellow-400'
                : 'text-gray-300 fill-gray-300 hover:text-yellow-300 hover:fill-yellow-300'
          }`}
          onClick={() => handleClick(star)}
          onMouseEnter={() => setHoverRating(star)}
          onMouseLeave={() => setHoverRating(0)}
        >
          <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
        </svg>
      ))}
    </div>
  );
};

export default InteractiveRating;

这个组件添加了悬停效果和点击事件处理:

  • 鼠标悬停时预览评分效果
  • 点击时确认评分并通过回调函数传出结果
  • 使用Tailwind的transition实现平滑的颜色过渡效果

高级实现:使用mask技术的星级评分

twin.macro结合DaisyUI等插件可以实现更复杂的星级评分效果。项目测试用例中展示了使用mask技术的实现方式:

// 源自测试用例: [tests/__fixtures__/pluginDaisyUi/pluginDaisyUi.tsx](https://link.gitcode.com/i/fb79676766aa85a15c9f3b2d3d879eea)
<div tw="rating gap-1">
  <input type="radio" name="rating-3" tw="mask mask-heart bg-red-400" />
  <input type="radio" name="rating-3" tw="mask mask-heart bg-orange-400" />
  <input type="radio" name="rating-3" tw="mask mask-heart bg-yellow-400" />
  <input type="radio" name="rating-3" tw="mask mask-heart bg-lime-400" />
  <input type="radio" name="rating-3" tw="mask mask-heart bg-green-400" />
</div>

这个实现使用了Tailwind的mask工具类,将输入框渲染成心形。虽然示例中使用的心形,但我们可以很容易地修改为星形:

import tw from 'twin.macro';

const StyledRating = tw.div`rating gap-1`;
const StyledInput = tw.input`mask mask-star transition-all duration-300`;

const MaskedRating = ({ onRatingChange }) => {
  return (
    <StyledRating>
      {[1, 2, 3, 4, 5].map((star) => (
        <StyledInput
          key={star}
          type="radio"
          name="rating"
          tw={`${
            star === 1 ? 'bg-red-400' :
            star === 2 ? 'bg-orange-400' :
            star === 3 ? 'bg-yellow-400' :
            star === 4 ? 'bg-lime-400' : 'bg-green-400'
          }`}
          onClick={() => onRatingChange(star)}
        />
      ))}
    </StyledRating>
  );
};

export default MaskedRating;

这种方法的优势在于:

  • 使用原生表单控件,有更好的可访问性
  • 通过CSS mask实现复杂形状,无需额外图片
  • 可以轻松实现从低分到高分的渐变色效果

星级评分组件的高级特性

为了提升用户体验,我们可以为星级评分组件添加更多高级特性:

1. 半星评分支持

有些场景下需要支持半星评分,以下是实现方案:

import { useState } from 'react';
import tw from 'twin.macro';

const HalfStarRating = ({ onRatingChange }) => {
  const [rating, setRating] = useState(0);
  
  const handleClick = (e) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const clickX = e.clientX - rect.left;
    const halfWidth = rect.width / 2;
    const starValue = parseInt(e.currentTarget.dataset.star);
    
    // 判断点击位置是在星星的左半部分还是右半部分
    const finalRating = clickX < halfWidth ? starValue - 0.5 : starValue;
    setRating(finalRating);
    onRatingChange(finalRating);
  };
  
  return (
    <div tw="flex">
      {[1, 2, 3, 4, 5].map((star) => (
        <div 
          key={star} 
          data-star={star}
          tw="relative inline-block w-10 h-10 cursor-pointer"
          onClick={handleClick}
        >
          {/* 全星背景 */}
          <svg
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 24 24"
            tw={`absolute w-full h-full text-gray-300`}
          >
            <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
          </svg>
          
          {/* 填充星星 - 全星部分 */}
          {rating >= star && (
            <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              tw={`absolute w-full h-full text-yellow-400`}
            >
              <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
            </svg>
          )}
          
          {/* 填充星星 - 半星部分 */}
          {rating > star - 0.5 && rating < star && (
            <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              tw={`absolute w-full h-full text-yellow-400 clip-path-inset-0-right-1/2`}
            >
              <path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
            </svg>
          )}
        </div>
      ))}
      <span tw="ml-2 text-gray-700">{rating.toFixed(1)}</span>
    </div>
  );
};

export default HalfStarRating;

2. 星级评分组件的封装与复用

为了提高组件的可复用性,我们可以创建一个高度可配置的星级评分组件:

// components/Rating/styled.ts - 使用styled-components语法
import tw, { styled } from 'twin.macro';

export const RatingContainer = styled.div`
  ${tw`flex items-center`}
`;

export const Star = styled.svg`
  ${tw`w-8 h-8 transition-all duration-200 cursor-pointer`}
  
  ${({ isFilled, theme }) => isFilled && tw`text-yellow-400 fill-yellow-400`}
  ${({ isFilled, theme }) => !isFilled && tw`text-gray-300 fill-gray-300`}
  ${({ isHovered }) => isHovered && tw`scale-110`}
`;

export const RatingValue = styled.span`
  ${tw`ml-2 text-gray-600 font-medium`}
`;

// components/Rating/index.tsx
import { useState } from 'react';
import { RatingContainer, Star, RatingValue } from './styled';

interface RatingProps {
  initialRating?: number;
  maxStars?: number;
  size?: 'small' | 'medium' | 'large';
  color?: string;
  onRatingChange?: (rating: number) => void;
  allowHalfStars?: boolean;
}

const Rating = ({
  initialRating = 0,
  maxStars = 5,
  size = 'medium',
  color = 'yellow-400',
  onRatingChange,
  allowHalfStars = false
}: RatingProps) => {
  // 组件实现...
};

export default Rating;

这种封装方式使用了twin.macro的styled语法,将样式与逻辑分离,提高了代码的可维护性。更多关于styled-components与twin.macro结合使用的信息,可以参考官方文档docs/styled-component-guide.md

性能优化与最佳实践

在实现星级评分组件时,我们还需要考虑性能优化和最佳实践:

  1. 使用React.memo避免不必要的重渲染
const MemoizedRating = React.memo(Rating, (prevProps, nextProps) => {
  return (
    prevProps.initialRating === nextProps.initialRating &&
    prevProps.maxStars === nextProps.maxStars &&
    prevProps.size === nextProps.size
  );
});
  1. 使用CSS变量实现主题定制
import tw, { css } from 'twin.macro';

const ThemedRating = styled.div`
  --star-color: ${props => props.theme.starColor || '#facc15'};
  --empty-star-color: ${props => props.theme.emptyStarColor || '#e5e7eb'};
  
  ${tw`flex`}
  
  svg {
    ${tw`w-8 h-8 transition-colors duration-200`}
    fill: var(--empty-star-color);
    color: var(--empty-star-color);
    
    &.filled {
      fill: var(--star-color);
      color: var(--star-color);
    }
  }
`;
  1. 服务端渲染考虑: 对于使用Next.js等SSR框架的项目,需要确保twin.macro正确配置。相关配置可以参考twin.macro的SSR文档

总结与扩展

本文介绍了使用twin.macro实现星级评分系统的多种方法,从基础的静态展示到高级的交互式组件。我们学习了如何利用twin.macro的特性简化样式编写,以及如何结合React的状态管理实现交互功能。

星级评分系统可以进一步扩展,例如:

  • 添加动画效果,如星星填充动画
  • 实现评分统计和展示
  • 结合后端API保存用户评分

通过twin.macro,我们可以更高效地构建这类UI组件,同时保持代码的可维护性和可扩展性。更多关于twin.macro的高级用法,可以参考高级主题文档自定义配置指南

希望本文能帮助你在项目中实现出色的星级评分系统,提升用户体验和界面美观度。

【免费下载链接】twin.macro 🦹‍♂️ Twin blends the magic of Tailwind with the flexibility of css-in-js (emotion, styled-components, solid-styled-components, stitches and goober) at build time. 【免费下载链接】twin.macro 项目地址: https://gitcode.com/gh_mirrors/tw/twin.macro

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值