ข้ามไปยังเนื้อหา

บล็อก

การสร้างเว็บอินเตอร์เฟสที่น่าทึ่งด้วย React และ CSS สมัยใหม่

React และ CSS สมัยใหม่

  • อ่าน 5 นาที
  • Updated
ในหน้านี้

เว็บมีการพัฒนาไปไกลกว่าเพจแบบคงที่ ผู้ใช้คาดหวังอินเทอร์เฟซที่สวยงาม เรียบเนียน และทุกคนสามารถเข้าถึงได้ React ผสมผสานกับเทคนิค CSS สมัยใหม่ ช่วยให้นักพัฒนามีเครื่องมือในการตอบสนองความคาดหวังเหล่านั้น คู่มือนี้ครอบคลุมทุกอย่างตั้งแต่ระบบเค้าโครงพื้นฐานไปจนถึงภาพเคลื่อนไหวขั้นสูงและสถาปัตยกรรมระบบการออกแบบ

เค้าโครง CSS สมัยใหม่: ตารางและ Flexbox

React สถาปัตยกรรมส่วนประกอบApp Shellส่วนหัว - การนำทาง โลโก้ ค้นหา เมนูผู้ใช้แถบด้านข้างลิงก์นำทางตัวกรองหมวดหมู่การดำเนินการด่วนเนื้อหาหลักการ์ดการ์ดแบบฟอร์มส่วนประกอบส่งการ์ดสถานะ & โฟลว์ข้อมูลProps ↓ข้อมูลไหลลงจากพาเรนต์ถึงคอมโพเนนต์ย่อยการยกสถานะ ↑เหตุการณ์ & การเรียกกลับไหลขึ้นจากลูกไปยังพาเรนต์บริบทที่ใช้ร่วมกันข้ามต้นไม้

CSS Grid และ Flexbox เป็นเสาหลักสองประการของรูปแบบสมัยใหม่ การทำความเข้าใจว่าเมื่อใดควรใช้แต่ละรายการเป็นสิ่งสำคัญสำหรับการสร้างอินเทอร์เฟซแบบตอบสนอง

CSS Grid สำหรับเลย์เอาต์แบบสองมิติ

Grid ใช้งานได้ดีกับเลย์เอาต์ระดับหน้าและการออกแบบใดๆ ที่ต้องการการควบคุมทั้งแถวและคอลัมน์พร้อมกัน:

.dashboard {
  display: grid;
  grid-template-columns: 250px 1fr 300px;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "sidebar header  aside"
    "sidebar main    aside"
    "sidebar footer  aside";
  gap: 1rem;
  min-height: 100vh;
}

.header  { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main    { grid-area: main; }
.aside   { grid-area: aside; }
.footer  { grid-area: footer; }

Flexbox สำหรับโฟลว์มิติเดียว

Flexbox เหมาะสำหรับเลย์เอาต์ระดับส่วนประกอบ — แถบนำทาง แถวการ์ด เลย์เอาต์แบบฟอร์ม และเนื้อหาใดๆ ที่ ไหลไปในทิศทางเดียว:

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 2rem;
}

.nav-links {
  display: flex;
  gap: 1.5rem;
  list-style: none;
}

Container Queries

Container Queries เป็นตัวเปลี่ยนเกมสำหรับการออกแบบตามส่วนประกอบ แตกต่างจากคิวรีสื่อที่ตอบสนองต่อความกว้างวิวพอร์ต คิวรีคอนเทนเนอร์ตอบสนองต่อขนาดของคอนเทนเนอร์ของคอมโพเนนต์ ซึ่งหมายความว่าส่วนประกอบของการ์ดสามารถปรับเค้าโครงได้ไม่ว่าจะปรากฏในแถบด้านข้างแคบหรือพื้นที่เนื้อหาหลักที่กว้าง:

.card-container {
  container-type: inline-size;
  container-name: card;
}

@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 1rem;
  }
}

@container card (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

CSS คุณสมบัติที่กำหนดเองและโทเค็นการออกแบบ

คุณสมบัติที่กำหนดเอง CSS (ตัวแปร) เป็นรากฐานของระบบการออกแบบที่สามารถบำรุงรักษาได้ กำหนดโทเค็นการออกแบบของคุณเป็นคุณสมบัติที่กำหนดเองบนองค์ประกอบ:rootและอ้างอิงถึงตลอดทั้งสไตล์ของคุณ:

:root {
  /* Colors */
  --color-primary: #3b82f6;
  --color-primary-dark: #1d4ed8;
  --color-surface: #ffffff;
  --color-surface-elevated: #f8fafc;
  --color-text: #0f172a;
  --color-text-muted: #64748b;

  /* Spacing */
  --space-xs: 0.25rem;
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 1.5rem;
  --space-xl: 2rem;

  /* Typography */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', monospace;
  --text-sm: 0.875rem;
  --text-base: 1rem;
  --text-lg: 1.125rem;
  --text-xl: 1.25rem;

  /* Shadows */
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px rgb(0 0 0 / 0.07);
  --shadow-lg: 0 10px 15px rgb(0 0 0 / 0.1);

  /* Borders */
  --radius-sm: 0.375rem;
  --radius-md: 0.5rem;
  --radius-lg: 0.75rem;
  --radius-full: 9999px;
}

โทเค็นการออกแบบช่วยให้มั่นใจถึงความสอดคล้องกันในแอปพลิเคชันทั้งหมดของคุณ และทำให้การเปลี่ยนแปลงสไตล์ทั่วโลกเป็นเรื่องเล็กน้อย — อัปเดตตัวแปรหนึ่งตัวแปรและทุกองค์ประกอบที่ใช้จะอัปเดตโดยอัตโนมัติ

แนวทางการจัดสไตล์ใน React

React นำเสนอแนวทางการจัดสไตล์หลายประการ แต่ละอย่างมีข้อแลกเปลี่ยนในด้านประสบการณ์ ประสิทธิภาพ และการบำรุงรักษาของนักพัฒนา

โมดูล CSS

โมดูล CSS กำหนดขอบเขตสไตล์ให้กับแต่ละส่วนประกอบโดยการสร้างชื่อคลาสที่ไม่ซ้ำกัน ณ เวลาที่สร้าง โดยนำเสนอความสมดุลระหว่างความเรียบง่ายและปลอดภัย:

/* Button.module.css */
.button {
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-md);
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
}

.primary {
  background: var(--color-primary);
  color: white;
}

.primary:hover {
  background: var(--color-primary-dark);
  box-shadow: var(--shadow-md);
}
import styles from './Button.module.css';

function Button({ variant = 'primary', children, ...props }) {
  return (
    <button
      className={`${styles.button} ${styles[variant]}`}
      {...props}
    >
      {children}
    </button>
  );
}

Tailwind CSS

Tailwind CSS มีคลาสยูทิลิตี้ที่ให้คุณจัดสไตล์ส่วนประกอบใน JSX ได้โดยตรง ช่วยลดการสลับบริบทระหว่างไฟล์ CSS และ JavaScript และบังคับใช้ระบบการออกแบบที่สอดคล้องกันผ่านการกำหนดค่า:

function Card({ title, description, image }) {
  return (
    <div className="rounded-lg shadow-md overflow-hidden
                    bg-white hover:shadow-lg transition-shadow">
      <img src={image} alt={title}
           className="w-full h-48 object-cover" />
      <div className="p-4">
        <h3 className="text-lg font-semibold text-gray-900">
          {title}
        </h3>
        <p className="mt-2 text-gray-600 text-sm">
          {description}
        </p>
      </div>
    </div>
  );
}

CSS-in-JS (styled-components, Emotion)

ไลบรารี CSS-in-JS ช่วยให้คุณสามารถเขียน CSS ได้โดยตรงใน JavaScript ของคุณ พร้อมสิทธิ์เข้าถึงอุปกรณ์ประกอบฉากและสถานะเต็มรูปแบบ แม้ว่ามีประสิทธิภาพ แต่ก็เพิ่มโอเวอร์เฮดรันไทม์และกำลังไม่ได้รับความนิยมมากขึ้นเรื่อยๆ สำหรับแอปพลิเคชันที่แสดงผลบนเซิร์ฟเวอร์:

import styled from 'styled-components';

const Badge = styled.span`
  display: inline-flex;
  align-items: center;
  padding: 0.25rem 0.75rem;
  border-radius: 9999px;
  font-size: 0.75rem;
  font-weight: 600;
  background: ${props => props.variant === 'success'
    ? '#dcfce7' : '#dbeafe'};
  color: ${props => props.variant === 'success'
    ? '#166534' : '#1e40af'};
`;

สำหรับโปรเจ็กต์ใหม่ แนะนำให้ใช้โมดูล CSS หรือ Tailwind CSS แทน CSS-in-JS เนื่องจากประสิทธิภาพที่ดีขึ้นและความเข้ากันได้ในการเรนเดอร์เซิร์ฟเวอร์

การสร้างระบบการออกแบบด้วย Storybook

เทคโนโลยี UI สมัยใหม่ Stackระบบการออกแบบโทเค็น, การพิมพ์, จานสี, สเกลระยะห่าง, แนวทางของแบรนด์Component Libraryปุ่ม, การ์ด, Modals, แบบฟอร์ม, ตาราง — บันทึกไว้ใน StorybookTheme EngineCSS คุณสมบัติที่กำหนดเอง, โหมดมืด/สว่าง, ไดนามิก ธีมรูปแบบตอบสนองตาราง, Flexbox, แบบสอบถามคอนเทนเนอร์, การพิมพ์ของเหลวพร้อมแคลมป์ ()เลเยอร์การเข้าถึงบทบาท ARIA, การนำทางด้วยแป้นพิมพ์, การจัดการโฟกัส, อัตราส่วนความคมชัด54321แต่ละเลเยอร์สร้างขึ้นจากเลเยอร์ด้านล่าง — การเข้าถึงเป็นรากฐานHigher Abstraction →

Storybook มอบสภาพแวดล้อมที่แยกออกมาสำหรับการพัฒนาและบันทึกส่วนประกอบ UI โดยทำหน้าที่เป็นทั้งเครื่องมือในการพัฒนาและแนวทางการใช้ชีวิต:

// Button.stories.jsx
import { Button } from './Button';

export default {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    variant: {
      options: ['primary', 'secondary', 'ghost'],
      control: { type: 'select' },
    },
    size: {
      options: ['sm', 'md', 'lg'],
      control: { type: 'select' },
    },
  },
};

export const Primary = {
  args: { variant: 'primary', children: 'Click me' },
};

export const Secondary = {
  args: { variant: 'secondary', children: 'Click me' },
};

Storybook ช่วยให้นักออกแบบและนักพัฒนาสามารถทำงานร่วมกันในส่วนประกอบต่างๆ โดยแยกออกจากกัน ทำให้มั่นใจได้ว่าแต่ละส่วนประกอบจะทำงานได้อย่างถูกต้องก่อนที่จะรวมเข้ากับแอปพลิเคชัน

การโต้ตอบแบบย่อยและแอนิเมชั่น

แอนิเมชั่นที่ใส่ใจจะแนะนำผู้ใช้ ให้ข้อเสนอแนะ และสร้างความรู้สึกที่สวยงาม Framer Motion คือไลบรารีแอนิเมชันชั้นนำสำหรับ React

แอนิเมชั่นพื้นฐานพร้อม Framer Motion

import { motion } from 'framer-motion';

function FadeInCard({ children }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.4, ease: 'easeOut' }}
    >
      {children}
    </motion.div>
  );
}

แอนิเมชั่นรายการเซ

import { motion } from 'framer-motion';

const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: { staggerChildren: 0.1 },
  },
};

const item = {
  hidden: { opacity: 0, x: -20 },
  show: { opacity: 1, x: 0 },
};

function AnimatedList({ items }) {
  return (
    <motion.ul variants={container}
      initial="hidden" animate="show">
      {items.map(i => (
        <motion.li key={i.id} variants={item}>
          {i.label}
        </motion.li>
      ))}
    </motion.ul>
  );
}

การโต้ตอบแบบไมโคร CSS เท่านั้น

ไม่ใช่ทุกแอนิเมชั่นจำเป็นต้องมีไลบรารี การเปลี่ยนและการแปลง CSS จัดการการโต้ตอบทั่วไปหลายอย่างได้อย่างมีประสิทธิภาพ:

.interactive-card {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.interactive-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
}

.button-press:active {
  transform: scale(0.97);
}

รูปแบบส่วนประกอบที่เข้าถึงได้

การเข้าถึงไม่ได้เป็นทางเลือก — เป็นข้อกำหนดสำหรับอินเทอร์เฟซเว็บระดับมืออาชีพ ปฏิบัติตามรูปแบบเหล่านี้เพื่อให้แน่ใจว่าส่วนประกอบของคุณใช้งานได้สำหรับทุกคน

การจัดการโฟกัส

เมื่อโมดัลเปิดขึ้น ให้ย้ายโฟกัสไปที่โมดอล เมื่อปิดแล้ว ให้กลับมาโฟกัสไปที่องค์ประกอบทริกเกอร์ ใช้แอตทริบิวต์inertเพื่อป้องกันการโต้ตอบกับเนื้อหาพื้นหลัง:

function Modal({ isOpen, onClose, children }) {
  const closeRef = useRef();
  const previousFocus = useRef();

  useEffect(() => {
    if (isOpen) {
      previousFocus.current = document.activeElement;
      closeRef.current?.focus();
    } else {
      previousFocus.current?.focus();
    }
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div role="dialog" aria-modal="true">
      <button ref={closeRef} onClick={onClose}
        aria-label="Close dialog">X</button>
      {children}
    </div>
  );
}

การนำทางด้วยแป้นพิมพ์

องค์ประกอบแบบโต้ตอบทั้งหมดต้องสามารถเข้าถึงได้ด้วยแป้นพิมพ์ ส่วนประกอบที่กำหนดเอง เช่น เมนูแบบเลื่อนลง แท็บ และภาพหมุนควรเป็นไปตามรูปแบบการออกแบบ WAI-ARIA สำหรับการโต้ตอบกับแป้นพิมพ์

คอนทราสต์ของสีและการออกแบบภาพ

ตรวจสอบให้แน่ใจว่าข้อความตรงตามอัตราส่วนคอนทราสต์ WCAG 2.1: 4.5:1 สำหรับข้อความปกติ, 3:1 สำหรับข้อความขนาดใหญ่ อย่าพึ่งพาสีเพียงอย่างเดียวในการถ่ายทอดข้อมูล ใช้ไอคอน ป้ายข้อความ หรือรูปแบบเป็นตัวบ่งชี้เสริม

กลยุทธ์การออกแบบที่ตอบสนอง

การออกแบบที่ตอบสนองสมัยใหม่เป็นมากกว่าจุดพัก ใช้วิธีการออกแบบที่ลื่นไหลซึ่งปรับให้เข้ากับหน้าจอทุกขนาดได้อย่างราบรื่น

การพิมพ์แบบไหล

ใช้clamp()เพื่อสร้างการพิมพ์ที่ปรับขนาดได้อย่างราบรื่นระหว่างขนาดขั้นต่ำและสูงสุด:

h1 {
  font-size: clamp(1.75rem, 4vw, 3rem);
  line-height: 1.2;
}

p {
  font-size: clamp(0.9rem, 1.5vw, 1.125rem);
  line-height: 1.6;
}

ระยะห่างที่ตอบสนอง

ใช้หลักการของเหลวเดียวกันกับระยะห่างและเค้าโครง:

.section {
  padding: clamp(2rem, 5vw, 4rem) clamp(1rem, 3vw, 2rem);
}

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: clamp(1rem, 2vw, 2rem);
}

โหมดมืด การใช้งาน

โหมดมืดต้องการมากกว่าการกลับสี ออกแบบจานสีแยกต่างหากที่รักษาคอนทราสต์และลำดับชั้นของภาพที่เหมาะสม:

:root {
  --bg-primary: #ffffff;
  --bg-secondary: #f1f5f9;
  --text-primary: #0f172a;
  --text-secondary: #475569;
  --border-color: #e2e8f0;
}

[data-theme='dark'] {
  --bg-primary: #0f172a;
  --bg-secondary: #1e293b;
  --text-primary: #f1f5f9;
  --text-secondary: #94a3b8;
  --border-color: #334155;
}

ใน React จัดการธีมด้วยผู้ให้บริการบริบทและคงการตั้งค่าผู้ใช้ไว้เป็น localStorage:

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState(() => {
    if (typeof window === 'undefined') return 'light';
    return localStorage.getItem('theme') ||
      (window.matchMedia('(prefers-color-scheme: dark)').matches
        ? 'dark' : 'light');
  });

  useEffect(() => {
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('theme', theme);
  }, [theme]);

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

การเพิ่มประสิทธิภาพประสิทธิภาพ

อินเทอร์เฟซที่สวยงามจะต้องรวดเร็วเช่นกัน ใช้เทคนิคประสิทธิภาพ CSS เหล่านี้เพื่อให้ UI ของคุณตอบสนอง

การบรรจุ CSS

คุณสมบัติcontainจะบอกเบราว์เซอร์ว่าเนื้อหาขององค์ประกอบไม่ขึ้นอยู่กับส่วนที่เหลือของหน้า ทำให้สามารถเพิ่มประสิทธิภาพการเรนเดอร์ได้:

.card {
  contain: layout style paint;
}

.sidebar {
  contain: layout size style;
}

คุณสมบัติการเปลี่ยนแปลง

ใช้will-changeเพื่อบอกใบ้เบราว์เซอร์เกี่ยวกับภาพเคลื่อนไหวที่กำลังจะมาถึง ใช้เท่าที่จำเป็น — การใช้มากเกินไปอาจเพิ่มการใช้หน่วยความจำ:

.animated-element {
  will-change: transform, opacity;
}

/* Remove after animation completes */
.animated-element.done {
  will-change: auto;
}

การมองเห็นเนื้อหา

สำหรับหน้าที่ยาวและมีหลายส่วนcontent-visibility: autoช่วยให้เบราว์เซอร์ข้ามการแสดงเนื้อหานอกหน้าจอได้ทั้งหมด:

.blog-post-section {
  content-visibility: auto;
  contain-intrinsic-size: 0 500px;
}

บทสรุป

การสร้างอินเทอร์เฟซเว็บที่สวยงามเป็นศิลปะที่ผสมผสาน การออกแบบภาพ ทักษะทางเทคนิค และความเห็นอกเห็นใจต่อผู้ใช้ CSS สมัยใหม่มีระบบเลย์เอาต์ที่ทรงพลัง เทคนิคการออกแบบที่ไหลลื่น และการเพิ่มประสิทธิภาพ โมเดลส่วนประกอบของ React ทำให้ง่ายต่อการสรุปสไตล์ พฤติกรรม และความสามารถในการเข้าถึงลงในบล็อคส่วนประกอบที่นำกลับมาใช้ใหม่ได้ ด้วยการรวมเทคโนโลยีเหล่านี้เข้ากับแอนิเมชั่นที่รอบคอบ แนวทางปฏิบัติในการเข้าถึงที่มีประสิทธิภาพ และระบบการออกแบบที่มีโครงสร้างที่ดี คุณสามารถสร้างอินเทอร์เฟซที่ไม่เพียงแต่สวยงามเท่านั้น แต่ยังรวดเร็ว เข้าถึงได้ และบำรุงรักษาได้ เริ่มต้นด้วยรากฐานที่มั่นคงของโทเค็นการออกแบบและโครงร่างดั้งเดิม เลเยอร์ในการโต้ตอบและแอนิเมชั่น ทดสอบในอุปกรณ์และเทคโนโลยีช่วยเหลือ และทำซ้ำตามความคิดเห็นของผู้ใช้จริง