Skip to content

样式和CSS - 让你的小程序更好看

🎨 颜值即正义!今天我们来学习如何让小程序变得更加美观

1. UniApp样式基础

支持的CSS特性

scss
// UniApp支持大部分CSS3特性
.example {
  // 基础属性
  width: 100px;
  height: 100px;
  background-color: #007aff;
  
  // 圆角
  border-radius: 8px;
  
  // 阴影
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  
  // 渐变
  background: linear-gradient(45deg, #007aff, #5ac8fa);
  
  // 变换
  transform: scale(1.1) rotate(45deg);
  
  // 过渡动画
  transition: all 0.3s ease;
  
  // Flex布局
  display: flex;
  justify-content: center;
  align-items: center;
}

尺寸单位

scss
.size-demo {
  // px - 像素单位
  width: 100px;
  
  // rpx - 响应式像素(推荐)
  width: 200rpx;  // 在375px宽度设备上等于100px
  
  // % - 百分比
  width: 50%;
  
  // vh/vw - 视口单位
  height: 100vh;  // 视口高度
  width: 100vw;   // 视口宽度
}

2. SCSS预处理器

变量定义

scss
// uni.scss - 全局变量文件
$primary-color: #007aff;
$success-color: #4cd964;
$warning-color: #ff9500;
$error-color: #ff3b30;

$font-size-small: 24rpx;
$font-size-base: 28rpx;
$font-size-large: 32rpx;

$border-radius: 8rpx;
$spacing: 20rpx;

混合器(Mixins)

scss
// 常用混合器
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

@mixin ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

@mixin button-style($bg-color, $text-color: white) {
  background-color: $bg-color;
  color: $text-color;
  border: none;
  border-radius: $border-radius;
  padding: 20rpx 40rpx;
  
  &:active {
    opacity: 0.8;
  }
}

// 使用混合器
.my-button {
  @include button-style($primary-color);
}

.center-box {
  @include flex-center;
  height: 200rpx;
}

.text-ellipsis {
  @include ellipsis;
  width: 200rpx;
}

3. 响应式设计

使用rpx实现适配

vue
<template>
  <view class="responsive-demo">
    <view class="card">
      <image src="/static/avatar.png" class="avatar" />
      <view class="info">
        <text class="name">用户名称</text>
        <text class="desc">这是用户描述信息</text>
      </view>
      <button class="follow-btn">关注</button>
    </view>
  </view>
</template>

<style lang="scss">
.responsive-demo {
  padding: 30rpx;
}

.card {
  background: white;
  border-radius: 20rpx;
  padding: 30rpx;
  display: flex;
  align-items: center;
  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
}

.avatar {
  width: 100rpx;
  height: 100rpx;
  border-radius: 50rpx;
  margin-right: 30rpx;
}

.info {
  flex: 1;
}

.name {
  font-size: 32rpx;
  font-weight: bold;
  color: #333;
  display: block;
  margin-bottom: 10rpx;
}

.desc {
  font-size: 26rpx;
  color: #666;
}

.follow-btn {
  background: $primary-color;
  color: white;
  border: none;
  border-radius: 40rpx;
  padding: 16rpx 32rpx;
  font-size: 26rpx;
}
</style>

媒体查询

scss
// 针对不同屏幕尺寸的适配
.responsive-layout {
  padding: 20rpx;
  
  // 小屏幕(手机)
  @media screen and (max-width: 750rpx) {
    .grid {
      grid-template-columns: repeat(2, 1fr);
    }
  }
  
  // 大屏幕(平板)
  @media screen and (min-width: 750rpx) {
    .grid {
      grid-template-columns: repeat(4, 1fr);
    }
  }
}

4. 主题切换

实现暗黑模式

vue
<template>
  <view class="theme-demo" :class="{ 'dark-theme': isDark }">
    <view class="header">
      <text class="title">主题切换演示</text>
      <switch :checked="isDark" @change="toggleTheme" />
    </view>
    
    <view class="content">
      <view class="card">
        <text class="card-title">卡片标题</text>
        <text class="card-content">这是卡片内容,支持主题切换</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      isDark: false
    }
  },
  onLoad() {
    // 读取用户主题偏好
    const theme = uni.getStorageSync('theme')
    this.isDark = theme === 'dark'
  },
  methods: {
    toggleTheme(e) {
      this.isDark = e.detail.value
      
      // 保存主题偏好
      uni.setStorageSync('theme', this.isDark ? 'dark' : 'light')
    }
  }
}
</script>

<style lang="scss">
// 浅色主题变量
$light-bg: #ffffff;
$light-text: #333333;
$light-card-bg: #f8f9fa;

// 深色主题变量
$dark-bg: #1a1a1a;
$dark-text: #ffffff;
$dark-card-bg: #2d2d2d;

.theme-demo {
  min-height: 100vh;
  background: $light-bg;
  color: $light-text;
  transition: all 0.3s ease;
  
  &.dark-theme {
    background: $dark-bg;
    color: $dark-text;
    
    .card {
      background: $dark-card-bg;
    }
  }
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 30rpx;
  border-bottom: 1rpx solid #eee;
}

.title {
  font-size: 36rpx;
  font-weight: bold;
}

.content {
  padding: 30rpx;
}

.card {
  background: $light-card-bg;
  border-radius: 20rpx;
  padding: 30rpx;
  margin-bottom: 20rpx;
  transition: background 0.3s ease;
}

.card-title {
  font-size: 32rpx;
  font-weight: bold;
  display: block;
  margin-bottom: 20rpx;
}

.card-content {
  font-size: 28rpx;
  line-height: 1.6;
}
</style>

5. 动画效果

CSS动画

scss
// 淡入动画
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(20rpx);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

// 弹跳动画
@keyframes bounce {
  0%, 20%, 50%, 80%, 100% {
    transform: translateY(0);
  }
  40% {
    transform: translateY(-20rpx);
  }
  60% {
    transform: translateY(-10rpx);
  }
}

// 旋转动画
@keyframes spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}

.fade-in {
  animation: fadeIn 0.5s ease-out;
}

.bounce {
  animation: bounce 1s infinite;
}

.loading-spin {
  animation: spin 1s linear infinite;
}

过渡效果

vue
<template>
  <view class="transition-demo">
    <view 
      class="hover-card"
      :class="{ active: isActive }"
      @click="toggleActive"
    >
      <text>点击我</text>
    </view>
    
    <view class="slide-menu" :class="{ show: showMenu }">
      <text>侧边菜单</text>
    </view>
    
    <button @click="toggleMenu">切换菜单</button>
  </view>
</template>

<script>
export default {
  data() {
    return {
      isActive: false,
      showMenu: false
    }
  },
  methods: {
    toggleActive() {
      this.isActive = !this.isActive
    },
    toggleMenu() {
      this.showMenu = !this.showMenu
    }
  }
}
</script>

<style lang="scss">
.hover-card {
  width: 200rpx;
  height: 200rpx;
  background: $primary-color;
  border-radius: 20rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  transition: all 0.3s ease;
  transform: scale(1);
  
  &.active {
    transform: scale(1.1);
    background: $success-color;
    border-radius: 50rpx;
  }
}

.slide-menu {
  position: fixed;
  top: 0;
  left: -300rpx;
  width: 300rpx;
  height: 100vh;
  background: white;
  box-shadow: 2rpx 0 10rpx rgba(0, 0, 0, 0.1);
  transition: left 0.3s ease;
  z-index: 1000;
  
  &.show {
    left: 0;
  }
}
</style>

6. 实战案例:美化登录页面

vue
<template>
  <view class="login-page">
    <!-- 背景装饰 -->
    <view class="bg-decoration">
      <view class="circle circle-1"></view>
      <view class="circle circle-2"></view>
      <view class="circle circle-3"></view>
    </view>
    
    <!-- 登录表单 -->
    <view class="login-form">
      <view class="logo-section">
        <image src="/static/logo.png" class="logo" />
        <text class="app-name">我的应用</text>
        <text class="welcome-text">欢迎回来</text>
      </view>
      
      <view class="form-section">
        <view class="input-group">
          <view class="input-wrapper">
            <text class="input-icon">📱</text>
            <input 
              v-model="phone"
              placeholder="请输入手机号"
              class="form-input"
              type="number"
            />
          </view>
        </view>
        
        <view class="input-group">
          <view class="input-wrapper">
            <text class="input-icon">🔒</text>
            <input 
              v-model="password"
              placeholder="请输入密码"
              class="form-input"
              type="password"
            />
          </view>
        </view>
        
        <button 
          class="login-btn"
          :class="{ loading: isLoading }"
          @click="handleLogin"
        >
          <text v-if="!isLoading">登录</text>
          <view v-else class="loading-content">
            <view class="loading-spinner"></view>
            <text>登录中...</text>
          </view>
        </button>
        
        <view class="form-footer">
          <text class="link-text">忘记密码?</text>
          <text class="link-text">立即注册</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      phone: '',
      password: '',
      isLoading: false
    }
  },
  methods: {
    async handleLogin() {
      if (!this.phone || !this.password) {
        uni.showToast({
          title: '请填写完整信息',
          icon: 'error'
        })
        return
      }
      
      this.isLoading = true
      
      try {
        // 模拟登录请求
        await new Promise(resolve => setTimeout(resolve, 2000))
        
        uni.showToast({
          title: '登录成功',
          icon: 'success'
        })
        
        // 跳转到首页
        uni.reLaunch({
          url: '/pages/index/index'
        })
        
      } catch (error) {
        uni.showToast({
          title: '登录失败',
          icon: 'error'
        })
      } finally {
        this.isLoading = false
      }
    }
  }
}
</script>

<style lang="scss">
.login-page {
  min-height: 100vh;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  position: relative;
  overflow: hidden;
}

.bg-decoration {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  
  .circle {
    position: absolute;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.1);
    
    &.circle-1 {
      width: 200rpx;
      height: 200rpx;
      top: 10%;
      right: -50rpx;
      animation: float 6s ease-in-out infinite;
    }
    
    &.circle-2 {
      width: 150rpx;
      height: 150rpx;
      top: 60%;
      left: -30rpx;
      animation: float 8s ease-in-out infinite reverse;
    }
    
    &.circle-3 {
      width: 100rpx;
      height: 100rpx;
      top: 30%;
      left: 20%;
      animation: float 10s ease-in-out infinite;
    }
  }
}

@keyframes float {
  0%, 100% {
    transform: translateY(0);
  }
  50% {
    transform: translateY(-20rpx);
  }
}

.login-form {
  position: relative;
  z-index: 1;
  padding: 100rpx 60rpx;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 100vh;
}

.logo-section {
  text-align: center;
  margin-bottom: 80rpx;
  
  .logo {
    width: 120rpx;
    height: 120rpx;
    border-radius: 60rpx;
    margin-bottom: 30rpx;
  }
  
  .app-name {
    font-size: 48rpx;
    font-weight: bold;
    color: white;
    display: block;
    margin-bottom: 10rpx;
  }
  
  .welcome-text {
    font-size: 28rpx;
    color: rgba(255, 255, 255, 0.8);
  }
}

.form-section {
  .input-group {
    margin-bottom: 40rpx;
  }
  
  .input-wrapper {
    background: rgba(255, 255, 255, 0.9);
    border-radius: 50rpx;
    display: flex;
    align-items: center;
    padding: 0 30rpx;
    backdrop-filter: blur(10rpx);
  }
  
  .input-icon {
    font-size: 32rpx;
    margin-right: 20rpx;
  }
  
  .form-input {
    flex: 1;
    height: 100rpx;
    font-size: 32rpx;
    border: none;
    background: transparent;
  }
  
  .login-btn {
    width: 100%;
    height: 100rpx;
    background: linear-gradient(45deg, #ff6b6b, #ff8e8e);
    color: white;
    border: none;
    border-radius: 50rpx;
    font-size: 36rpx;
    font-weight: bold;
    margin-top: 40rpx;
    box-shadow: 0 10rpx 30rpx rgba(255, 107, 107, 0.3);
    transition: all 0.3s ease;
    
    &:active {
      transform: translateY(2rpx);
      box-shadow: 0 5rpx 15rpx rgba(255, 107, 107, 0.3);
    }
    
    &.loading {
      background: #ccc;
      box-shadow: none;
    }
  }
  
  .loading-content {
    display: flex;
    align-items: center;
    justify-content: center;
    
    .loading-spinner {
      width: 40rpx;
      height: 40rpx;
      border: 4rpx solid rgba(255, 255, 255, 0.3);
      border-top: 4rpx solid white;
      border-radius: 50%;
      animation: spin 1s linear infinite;
      margin-right: 20rpx;
    }
  }
  
  .form-footer {
    display: flex;
    justify-content: space-between;
    margin-top: 40rpx;
    
    .link-text {
      color: rgba(255, 255, 255, 0.8);
      font-size: 28rpx;
    }
  }
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>

小结

今天我们学习了:

  • ✅ UniApp样式基础和支持的CSS特性
  • ✅ SCSS预处理器的使用
  • ✅ 响应式设计和rpx单位
  • ✅ 主题切换的实现
  • ✅ CSS动画和过渡效果
  • ✅ 实战案例:美化登录页面

样式开发要点

  • 优先使用rpx实现响应式适配
  • 合理使用SCSS提高开发效率
  • 注意性能,避免过度使用动画
  • 保持设计的一致性

下一篇预告

下一篇我们将学习《数据绑定和事件处理 - 让页面动起来》,深入了解Vue.js在UniApp中的应用。


好看的界面是成功的一半,用心设计每一个细节,用户会感受到你的用心!