Appearance
Sass 实战案例
本章将通过实际项目案例来展示如何在真实场景中应用Sass,包括完整的项目搭建、组件开发、响应式设计实现等。
案例一:企业官网样式系统
项目初始化
scss
// abstracts/_variables.scss
// 企业官网设计系统变量
$brand-colors: (
primary: #2563eb,
secondary: #64748b,
accent: #f59e0b,
success: #10b981,
warning: #f59e0b,
danger: #ef4444,
dark: #1e293b,
light: #f8fafc
);
$typography: (
family-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif,
family-serif: 'Playfair Display', Georgia, 'Times New Roman', serif,
family-mono: 'Fira Code', Consolas, monospace,
sizes: (
xs: 0.75rem,
sm: 0.875rem,
base: 1rem,
lg: 1.125rem,
xl: 1.25rem,
'2xl': 1.5rem,
'3xl': 1.875rem,
'4xl': 2.25rem,
'5xl': 3rem,
'6xl': 3.75rem
),
weights: (
thin: 100,
extralight: 200,
light: 300,
normal: 400,
medium: 500,
semibold: 600,
bold: 700,
extrabold: 800,
black: 900
)
);
$spacing: (
0: 0,
1: 0.25rem,
2: 0.5rem,
3: 0.75rem,
4: 1rem,
5: 1.25rem,
6: 1.5rem,
8: 2rem,
10: 2.5rem,
12: 3rem,
16: 4rem,
20: 5rem,
24: 6rem,
32: 8rem
);
$breakpoints: (
sm: 640px,
md: 768px,
lg: 1024px,
xl: 1280px,
'2xl': 1536px
);
$border-radius: (
none: 0,
sm: 0.125rem,
default: 0.25rem,
md: 0.375rem,
lg: 0.5rem,
xl: 0.75rem,
'2xl': 1rem,
full: 9999px
);
$shadows: (
sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05),
default: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
md: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1)
);
scss
// abstracts/_functions.scss
@function brand-color($key) {
@return map-get($brand-colors, $key);
}
@function font-size($key) {
@return map-get(map-get($typography, sizes), $key);
}
@function font-weight($key) {
@return map-get(map-get($typography, weights), $key);
}
@function spacing($key) {
@return map-get($spacing, $key);
}
@function breakpoint($key) {
@return map-get($breakpoints, $key);
}
@function border-radius($key) {
@return map-get($border-radius, $key);
}
@function shadow($key) {
@return map-get($shadows, $key);
}
scss
// abstracts/_mixins.scss
@mixin respond-above($breakpoint) {
@media (min-width: breakpoint($breakpoint)) {
@content;
}
}
@mixin respond-below($breakpoint) {
@media (max-width: breakpoint($breakpoint) - 1px) {
@content;
}
}
@mixin respond-between($lower, $upper) {
@media (min-width: breakpoint($lower)) and (max-width: breakpoint($upper) - 1px) {
@content;
}
}
@mixin container {
width: 100%;
padding-right: spacing(4);
padding-left: spacing(4);
margin-right: auto;
margin-left: auto;
@include respond-above(lg) {
max-width: 1024px;
}
@include respond-above(xl) {
max-width: 1280px;
}
}
@mixin button-base {
display: inline-flex;
align-items: center;
justify-content: center;
font-family: map-get($typography, family-sans);
font-weight: font-weight(semibold);
text-decoration: none;
border: none;
cursor: pointer;
transition: all 0.2s ease;
border-radius: border-radius(default);
&:hover {
transform: translateY(-1px);
box-shadow: shadow(md);
}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
@mixin card {
background-color: white;
border-radius: border-radius(lg);
box-shadow: shadow(default);
overflow: hidden;
}
组件开发
scss
// components/_header.scss
.header {
background-color: white;
box-shadow: shadow(default);
position: sticky;
top: 0;
z-index: 50;
@include respond-above(lg) {
padding: spacing(4) 0;
}
&__container {
@include container;
display: flex;
justify-content: space-between;
align-items: center;
}
&__logo {
font-family: map-get($typography, family-serif);
font-size: font-size('xl');
font-weight: font-weight(bold);
color: brand-color(primary);
text-decoration: none;
@include respond-above(md) {
font-size: font-size('2xl');
}
}
&__nav {
display: none;
@include respond-above(lg) {
display: flex;
align-items: center;
}
}
&__nav-list {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: spacing(6);
}
&__nav-link {
text-decoration: none;
color: brand-color(dark);
font-weight: font-weight(medium);
padding: spacing(2) spacing(3);
border-radius: border-radius(default);
transition: color 0.2s ease;
&:hover {
color: brand-color(primary);
background-color: rgba(brand-color(primary), 0.1);
}
&--active {
color: brand-color(primary);
background-color: rgba(brand-color(primary), 0.1);
}
}
&__mobile-toggle {
display: flex;
flex-direction: column;
justify-content: space-between;
width: 30px;
height: 21px;
background: transparent;
border: none;
cursor: pointer;
padding: 0;
z-index: 100;
@include respond-above(lg) {
display: none;
}
&-line {
width: 100%;
height: 3px;
background-color: brand-color(dark);
border-radius: 2px;
transition: all 0.3s ease;
}
}
}
// 移动端导航
.mobile-nav {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(white, 0.95);
backdrop-filter: blur(10px);
z-index: 40;
display: flex;
flex-direction: column;
padding: spacing(6) spacing(4);
transform: translateX(-100%);
transition: transform 0.3s ease;
&--open {
transform: translateX(0);
}
&__close {
align-self: flex-end;
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
padding: spacing(2);
margin-bottom: spacing(6);
}
&__menu {
list-style: none;
margin: 0;
padding: 0;
}
&__item {
margin-bottom: spacing(4);
}
&__link {
text-decoration: none;
color: brand-color(dark);
font-size: font-size(xl);
font-weight: font-weight(semibold);
display: block;
padding: spacing(3);
border-radius: border-radius(default);
&:hover {
background-color: rgba(brand-color(primary), 0.1);
color: brand-color(primary);
}
}
}
scss
// components/_hero.scss
.hero {
padding: spacing(12) spacing(4);
background: linear-gradient(135deg, brand-color(primary) 0%, brand-color(secondary) 100%);
color: white;
text-align: center;
@include respond-above(md) {
padding: spacing(20) spacing(4);
}
&__container {
@include container;
}
&__title {
font-size: font-size('4xl');
font-weight: font-weight(extrabold);
margin-bottom: spacing(4);
line-height: 1.2;
@include respond-above(md) {
font-size: font-size('5xl');
}
@include respond-above(lg) {
font-size: font-size('6xl');
}
}
&__subtitle {
font-size: font-size(lg);
margin-bottom: spacing(8);
max-width: 2xl;
margin-left: auto;
margin-right: auto;
@include respond-above(md) {
font-size: font-size(xl);
}
}
&__actions {
display: flex;
flex-direction: column;
gap: spacing(4);
align-items: center;
@include respond-above(sm) {
flex-direction: row;
justify-content: center;
}
}
&__button {
@include button-base;
background-color: brand-color(accent);
color: brand-color(dark);
padding: spacing(3) spacing(6);
font-size: font-size(lg);
&:hover {
background-color: darken(brand-color(accent), 10%);
}
}
&__button--secondary {
@include button-base;
background-color: transparent;
color: white;
padding: spacing(3) spacing(6);
font-size: font-size(lg);
border: 2px solid white;
&:hover {
background-color: rgba(white, 0.1);
}
}
}
scss
// components/_features.scss
.features {
padding: spacing(16) spacing(4);
@include respond-above(md) {
padding: spacing(20) spacing(4);
}
&__container {
@include container;
}
&__header {
text-align: center;
margin-bottom: spacing(12);
}
&__title {
font-size: font-size('3xl');
font-weight: font-weight(bold);
color: brand-color(dark);
margin-bottom: spacing(3);
@include respond-above(md) {
font-size: font-size('4xl');
}
}
&__subtitle {
font-size: font-size(lg);
color: brand-color(secondary);
max-width: 2xl;
margin: 0 auto;
}
&__grid {
display: grid;
grid-template-columns: 1fr;
gap: spacing(8);
@include respond-above(md) {
grid-template-columns: repeat(2, 1fr);
gap: spacing(10);
}
@include respond-above(lg) {
grid-template-columns: repeat(3, 1fr);
}
}
&__item {
@include card;
padding: spacing(6);
text-align: center;
transition: transform 0.3s ease, box-shadow 0.3s ease;
&:hover {
transform: translateY(-5px);
box-shadow: shadow(lg);
}
}
&__icon {
width: 3rem;
height: 3rem;
background-color: rgba(brand-color(primary), 0.1);
border-radius: border-radius(full);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto spacing(4);
color: brand-color(primary);
font-size: font-size(xl);
}
&__item-title {
font-size: font-size(xl);
font-weight: font-weight(semibold);
color: brand-color(dark);
margin-bottom: spacing(3);
}
&__item-description {
color: brand-color(secondary);
line-height: 1.6;
}
}
scss
// components/_testimonials.scss
.testimonials {
padding: spacing(16) spacing(4);
background-color: brand-color(light);
@include respond-above(md) {
padding: spacing(20) spacing(4);
}
&__container {
@include container;
}
&__header {
text-align: center;
margin-bottom: spacing(12);
}
&__title {
font-size: font-size('3xl');
font-weight: font-weight(bold);
color: brand-color(dark);
margin-bottom: spacing(3);
@include respond-above(md) {
font-size: font-size('4xl');
}
}
&__subtitle {
font-size: font-size(lg);
color: brand-color(secondary);
max-width: 2xl;
margin: 0 auto;
}
&__carousel {
position: relative;
overflow: hidden;
border-radius: border-radius(xl);
}
&__slides {
display: flex;
transition: transform 0.5s ease;
}
&__slide {
min-width: 100%;
padding: spacing(8);
background-color: white;
box-shadow: shadow(md);
}
&__quote {
font-size: font-size(lg);
font-style: italic;
color: brand-color(dark);
text-align: center;
margin-bottom: spacing(6);
@include respond-above(md) {
font-size: font-size(xl);
}
}
&__author {
text-align: center;
}
&__author-name {
font-weight: font-weight(semibold);
color: brand-color(dark);
}
&__author-title {
color: brand-color(secondary);
font-size: font-size(sm);
}
&__controls {
display: flex;
justify-content: center;
gap: spacing(2);
margin-top: spacing(6);
}
&__control {
width: 12px;
height: 12px;
border-radius: border-radius(full);
background-color: brand-color(secondary);
border: none;
cursor: pointer;
transition: background-color 0.3s ease;
&--active {
background-color: brand-color(primary);
}
}
}
案例二:电商平台组件库
设计系统建立
scss
// ecom-abstracts/_variables.scss
// 电商专用变量
$ecom-colors: (
primary: #ff6b35, // 橙色主色
secondary: #2a9d8f, // 绿色辅助色
success: #2a9d8f, // 成功色
warning: #e9c46a, // 警告色
danger: #e76f51, // 危险色
info: #457b9d, // 信息色
background: #f8f9fa, // 背景色
surface: #ffffff, // 表面色
text-primary: #212529, // 主要文字
text-secondary: #6c757d, // 次要文字
border: #dee2e6 // 边框色
);
$ecom-typography: (
family-primary: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif,
family-display: 'Montserrat', sans-serif,
sizes: (
'2xs': 0.625rem, // 10px
xs: 0.75rem, // 12px
sm: 0.875rem, // 14px
base: 1rem, // 16px
lg: 1.125rem, // 18px
xl: 1.25rem, // 20px
'2xl': 1.5rem, // 24px
'3xl': 1.875rem, // 30px
'4xl': 2.25rem, // 36px
'5xl': 3rem, // 48px
'6xl': 3.75rem // 60px
)
);
$ecom-spacing: (
0: 0,
1: 0.25rem, // 4px
2: 0.5rem, // 8px
3: 0.75rem, // 12px
4: 1rem, // 16px
5: 1.25rem, // 20px
6: 1.5rem, // 24px
8: 2rem, // 32px
10: 2.5rem, // 40px
12: 3rem, // 48px
16: 4rem, // 64px
20: 5rem, // 80px
24: 6rem // 96px
);
$ecom-breakpoints: (
xs: 0,
sm: 576px,
md: 768px,
lg: 992px,
xl: 1200px,
xxl: 1400px
);
$ecom-grid: (
columns: 12,
gutter: 1.5rem,
container-max-widths: (
sm: 540px,
md: 720px,
lg: 960px,
xl: 1140px,
xxl: 1320px
)
);
产品卡片组件
scss
// ecom-components/_product-card.scss
@use '../ecom-abstracts/variables' as ecom;
.product-card {
background-color: map-get(ecom.$ecom-colors, surface);
border: 1px solid map-get(ecom.$ecom-colors, border);
border-radius: 0.5rem;
overflow: hidden;
transition: all 0.3s ease;
position: relative;
&:hover {
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
}
&__badge {
position: absolute;
top: 0.5rem;
left: 0.5rem;
background-color: map-get(ecom.$ecom-colors, danger);
color: white;
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-size: map-get(map-get(ecom.$ecom-typography, sizes), xs);
font-weight: 600;
z-index: 10;
&--new {
background-color: map-get(ecom.$ecom-colors, success);
}
&--sale {
background-color: map-get(ecom.$ecom-colors, warning);
color: #333;
}
}
&__image-container {
position: relative;
padding-top: 100%; // 1:1 aspect ratio
overflow: hidden;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.3) 100%);
}
}
&__image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
.product-card:hover & {
transform: scale(1.05);
}
}
&__info {
padding: 1rem;
}
&__category {
color: map-get(ecom.$ecom-colors, text-secondary);
font-size: map-get(map-get(ecom.$ecom-typography, sizes), xs);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0.25rem;
}
&__title {
font-size: map-get(map-get(ecom.$ecom-typography, sizes), base);
font-weight: 600;
color: map-get(ecom.$ecom-colors, text-primary);
margin-bottom: 0.5rem;
line-height: 1.4;
&:hover {
color: map-get(ecom.$ecom-colors, primary);
}
}
&__price-container {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.75rem;
}
&__current-price {
font-size: map-get(map-get(ecom.$ecom-typography, sizes), lg);
font-weight: 700;
color: map-get(ecom.$ecom-colors, primary);
}
&__original-price {
font-size: map-get(map-get(ecom.$ecom-typography, sizes), sm);
color: map-get(ecom.$ecom-colors, text-secondary);
text-decoration: line-through;
}
&__discount {
color: map-get(ecom.$ecom-colors, danger);
font-size: map-get(map-get(ecom.$ecom-typography, sizes), sm);
font-weight: 600;
}
&__rating {
display: flex;
align-items: center;
gap: 0.25rem;
margin-bottom: 0.75rem;
}
&__stars {
color: map-get(ecom.$ecom-colors, warning);
font-size: map-get(map-get(ecom.$ecom-typography, sizes), sm);
}
&__reviews {
color: map-get(ecom.$ecom-colors, text-secondary);
font-size: map-get(map-get(ecom.$ecom-typography, sizes), xs);
}
&__actions {
display: flex;
gap: 0.5rem;
}
&__action-button {
flex: 1;
padding: 0.5rem;
border: 1px solid map-get(ecom.$ecom-colors, border);
border-radius: 0.375rem;
background: white;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
border-color: map-get(ecom.$ecom-colors, primary);
color: map-get(ecom.$ecom-colors, primary);
}
&--add-to-cart {
background-color: map-get(ecom.$ecom-colors, primary);
color: white;
border-color: map-get(ecom.$ecom-colors, primary);
&:hover {
background-color: darken(map-get(ecom.$ecom-colors, primary), 10%);
border-color: darken(map-get(ecom.$ecom-colors, primary), 10%);
}
}
}
}
// 响应式产品卡片
@include media-query(map-get(ecom.$ecom-breakpoints, md)) {
.product-card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1.5rem;
}
}
@include media-query(map-get(ecom.$ecom-breakpoints, lg)) {
.product-card-grid {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
}
购物车组件
scss
// ecom-components/_cart.scss
.cart {
&__drawer {
position: fixed;
top: 0;
right: 0;
width: 100%;
max-width: 400px;
height: 100vh;
background: white;
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
&--open {
transform: translateX(0);
}
}
&__header {
padding: 1rem;
border-bottom: 1px solid map-get(ecom.$ecom-colors, border);
display: flex;
justify-content: space-between;
align-items: center;
}
&__title {
font-size: map-get(map-get(ecom.$ecom-typography, sizes), xl);
font-weight: 600;
color: map-get(ecom.$ecom-colors, text-primary);
}
&__close {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: map-get(ecom.$ecom-colors, text-secondary);
}
&__items {
padding: 1rem;
max-height: calc(100vh - 200px);
overflow-y: auto;
}
&__item {
display: flex;
gap: 1rem;
padding: 1rem 0;
border-bottom: 1px solid map-get(ecom.$ecom-colors, border);
}
&__item-image {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 0.5rem;
}
&__item-details {
flex: 1;
}
&__item-name {
font-weight: 600;
color: map-get(ecom.$ecom-colors, text-primary);
margin-bottom: 0.25rem;
}
&__item-price {
color: map-get(ecom.$ecom-colors, primary);
font-weight: 600;
margin-bottom: 0.5rem;
}
&__item-actions {
display: flex;
align-items: center;
gap: 0.5rem;
}
&__quantity {
display: flex;
align-items: center;
border: 1px solid map-get(ecom.$ecom-colors, border);
border-radius: 0.25rem;
&-button {
width: 2rem;
height: 2rem;
border: none;
background: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
&:hover {
background-color: map-get(ecom.$ecom-colors, background);
}
}
&-input {
width: 3rem;
text-align: center;
border: none;
border-left: 1px solid map-get(ecom.$ecom-colors, border);
border-right: 1px solid map-get(ecom.$ecom-colors, border);
}
}
&__item-remove {
color: map-get(ecom.$ecom-colors, danger);
background: none;
border: none;
cursor: pointer;
padding: 0.25rem;
&:hover {
color: darken(map-get(ecom.$ecom-colors, danger), 20%);
}
}
&__footer {
padding: 1rem;
border-top: 1px solid map-get(ecom.$ecom-colors, border);
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: white;
}
&__total {
display: flex;
justify-content: space-between;
font-size: map-get(map-get(ecom.$ecom-typography, sizes), lg);
font-weight: 600;
margin-bottom: 1rem;
}
&__checkout-button {
width: 100%;
padding: 1rem;
background-color: map-get(ecom.$ecom-colors, primary);
color: white;
border: none;
border-radius: 0.5rem;
font-size: map-get(map-get(ecom.$ecom-typography, sizes), lg);
font-weight: 600;
cursor: pointer;
transition: background-color 0.2s ease;
&:hover {
background-color: darken(map-get(ecom.$ecom-colors, primary), 10%);
}
}
}
案例三:仪表板界面
仪表板布局
scss
// dashboard/_layout.scss
.dashboard {
display: grid;
grid-template-areas:
"sidebar header"
"sidebar main";
grid-template-rows: 60px 1fr;
grid-template-columns: 250px 1fr;
height: 100vh;
background-color: #f8fafc;
@include respond-below(md) {
grid-template-areas:
"header"
"main";
grid-template-columns: 1fr;
grid-template-rows: 60px 1fr;
}
&__sidebar {
grid-area: sidebar;
background: white;
border-right: 1px solid #e2e8f0;
overflow-y: auto;
@include respond-below(md) {
position: fixed;
left: -250px;
top: 60px;
bottom: 0;
z-index: 90;
width: 250px;
transition: left 0.3s ease;
&--open {
left: 0;
}
}
}
&__header {
grid-area: header;
background: white;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1.5rem;
}
&__main {
grid-area: main;
overflow-y: auto;
padding: 1.5rem;
}
&__mobile-toggle {
display: none;
@include respond-below(md) {
display: block;
}
}
}
.sidebar {
&__logo {
padding: 1.5rem 1rem;
border-bottom: 1px solid #e2e8f0;
&-text {
font-size: 1.25rem;
font-weight: 700;
color: #1e293b;
}
}
&__nav {
padding: 1rem 0;
}
&__nav-item {
margin-bottom: 0.25rem;
&:last-child {
margin-bottom: 0;
}
}
&__nav-link {
display: flex;
align-items: center;
padding: 0.75rem 1rem;
color: #64748b;
text-decoration: none;
transition: all 0.2s ease;
&:hover {
background-color: #f1f5f9;
color: #1e293b;
}
&--active {
background-color: #dbeafe;
color: #1d4ed8;
border-left: 3px solid #3b82f6;
}
&-icon {
margin-right: 0.75rem;
width: 1.25rem;
height: 1.25rem;
}
&-text {
font-size: 0.875rem;
font-weight: 500;
}
}
}
数据可视化组件
scss
// dashboard/_charts.scss
.widget {
background: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
padding: 1.5rem;
margin-bottom: 1.5rem;
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
&__title {
font-size: 1.125rem;
font-weight: 600;
color: #1e293b;
}
&__subtitle {
font-size: 0.875rem;
color: #64748b;
}
&__chart-container {
height: 300px;
position: relative;
}
&__stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-top: 1.5rem;
}
&__stat {
text-align: center;
padding: 1rem;
background: #f8fafc;
border-radius: 0.5rem;
}
&__stat-value {
font-size: 1.5rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 0.25rem;
}
&__stat-label {
font-size: 0.875rem;
color: #64748b;
}
}
// 图表组件
.chart {
&__bar {
fill: #3b82f6;
&:hover {
fill: #1d4ed8;
}
}
&__line {
stroke: #3b82f6;
stroke-width: 2;
fill: none;
}
&__area {
fill: url(#gradient);
}
&__tooltip {
position: absolute;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 0.5rem;
border-radius: 0.25rem;
font-size: 0.75rem;
pointer-events: none;
z-index: 100;
}
}
通过这些实战案例,我们可以看到Sass在实际项目中的应用方式,包括设计系统建立、组件开发、响应式实现等方面。