You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Styled Components实现动画Button变体组件的复用与调用优化

Solution: Reuse Base Button for Animated Variant Without Code Duplication

Got it, let's fix that code duplication issue while keeping your desired API (<Button animatedButton text="test"/>) intact. Here's a step-by-step approach to reuse your base Button component for the animated variant:

1. Refactor the Base Button to Accept Variant Props

Instead of creating a separate AnimatedButton component, extend your existing Button to conditionally apply animated styles and markup based on a prop (like animatedButton). This way you reuse 100% of the base component's structure.

Example base component (updated):

import React, { useRef } from 'react';
import './Button.css';

const Button = ({ text, animatedButton = false, onClick, ...rest }) => {
  // Combine base class with animated variant class if needed
  const buttonClasses = `button ${animatedButton ? 'button--animated' : ''}`;
  const rippleRef = useRef(null);

  // Handle click + optional animation logic
  const handleClick = (e) => {
    // Execute the passed onClick handler first
    if (onClick) onClick(e);

    // Trigger ripple animation only if animatedButton is true
    if (animatedButton && rippleRef.current) {
      // Reset ripple state
      rippleRef.current.classList.remove('button__ripple--active');
      
      // Calculate click position relative to the button
      const buttonRect = e.currentTarget.getBoundingClientRect();
      const clickX = e.clientX - buttonRect.left;
      const clickY = e.clientY - buttonRect.top;

      // Position the ripple
      rippleRef.current.style.left = `${clickX}px`;
      rippleRef.current.style.top = `${clickY}px`;

      // Trigger animation with a tiny delay to ensure reset takes effect
      setTimeout(() => {
        rippleRef.current.classList.add('button__ripple--active');
      }, 10);
    }
  };

  return (
    <button className={buttonClasses} onClick={handleClick} {...rest}>
      {/* Reuse the base text span for both variants */}
      <span className="button__text">{text}</span>
      {/* Add ripple element only for animated variant */}
      {animatedButton && <span ref={rippleRef} className="button__ripple"></span>}
    </button>
  );
};

export default Button;

2. Write CSS That Reuses Base Styles + Extends Animated Styles

Avoid duplicating base button styles by nesting or extending them for the animated variant. Here's how to structure your CSS:

/* Base Button Styles (reused by both variants) */
.button {
  padding: 0.8rem 1.5rem;
  border: none;
  border-radius: 4px;
  font-size: 1rem;
  cursor: pointer;
  background-color: #007bff;
  color: white;
  position: relative;
  overflow: hidden;
  transition: background-color 0.2s ease;
}

.button:hover {
  background-color: #0056b3;
}

.button__text {
  position: relative;
  z-index: 1; /* Ensures text stays above ripple */
}

/* Animated Variant Extensions */
.button--animated {
  transition: transform 0.2s ease, background-color 0.2s ease;
}

.button--animated:active {
  transform: scale(0.95); /* Quick press animation */
}

/* Ripple Animation Styles */
.button__ripple {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.3);
  transform: translate(-50%, -50%);
  transition: width 0.6s ease, height 0.6s ease;
}

.button__ripple--active {
  width: 300px;
  height: 300px;
}

3. Use the Component As Requested

Now you can call both variants using the same Button component, just toggle the animatedButton prop:

// Basic Button
<Button text="Basic Action" onClick={() => console.log('Basic click!')} />

// Animated Button (matches your desired API)
<Button animatedButton text="Animated Action" onClick={() => console.log('Animated click!')} />

Bonus: Clean Up Complex Logic with Custom Hooks

If your animation logic gets more complex, extract it into a custom hook to keep the Button component clean:

// useButtonAnimation.js
import { useRef } from 'react';

export const useButtonAnimation = (isAnimated) => {
  const rippleRef = useRef(null);

  const handleAnimation = (e) => {
    if (!isAnimated || !rippleRef.current) return;

    rippleRef.current.classList.remove('button__ripple--active');
    const rect = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;

    rippleRef.current.style.left = `${x}px`;
    rippleRef.current.style.top = `${y}px`;

    setTimeout(() => {
      rippleRef.current.classList.add('button__ripple--active');
    }, 10);
  };

  return { rippleRef, handleAnimation };
};

Then use it in your Button component:

const Button = ({ text, animatedButton = false, onClick }) => {
  const { rippleRef, handleAnimation } = useButtonAnimation(animatedButton);
  const buttonClasses = `button ${animatedButton ? 'button--animated' : ''}`;

  const handleClick = (e) => {
    onClick?.(e);
    handleAnimation(e);
  };

  // Rest of the component markup remains the same
};

This approach keeps your code DRY, reuses all base Button markup/styles, and fits exactly the API you wanted.

内容的提问来源于stack exchange,提问作者Kiwimoisi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 20:12:32