vue3天气小组件

236 阅读3分钟

1753406033316.png ``

天气卡片组件(WeatherCard)

技术栈:Vue 3 + Composition API + CSS3 动画

功能特性

  1. 动态天气可视化

    • 太阳发光动画(支持自动/手动模式)
    • 浮动云层效果
    • 温度数字动态显示
  2. 智能交互设计

    • 悬停放大效果(带缓动动画)

    • 移动端响应式适配

    • 指针事件穿透优化

    1. 配置灵活性
    vue
    	<WeatherCard 
    
    	  :location="北京, 中国" 
    
    	  :temperature="25"
    
    	  :autoAnimate="true"
    
    	/>
    
  3. 性能优化

    • CSS will-change 属性提升动画性能
    • 动态计算阴影减少重绘
    • 防抖处理窗口 resize 事件

技术亮点

  1. 动画系统

    • 双模式动画控制(自动轮播/悬停触发)
    • 使用 @keyframes 实现太阳脉冲效果
    • 云层浮动采用 translateZ(0) 开启 GPU 加速
```<template>
  <div class="card" @mouseenter="hover = true" @mouseleave="hover = false">
    <div class="container">
      <div class="cloud front">
        <span class="left-front"></span>
        <span class="right-front"></span>
      </div>
      <span class="sun" :class="{ sunshine: animateSun }"></span>
      <div class="cloud back">
        <span class="left-back"></span>
        <span class="right-back"></span>
      </div>
    </div>

    <div class="card-header">
      <span>{{ location }}</span>
      <span>{{ date }}</span>
    </div>

    <span class="temp">{{ temperature }}°</span>

    <div class="temp-scale">
      <span>{{ scale }}</span>
    </div>
  </div>
</template>

<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';

export default {
  name: 'WeatherCard',
  props: {
    location: {
      type: String,
      default: 'Messadine, Susah<br>Tunisia'
    },
    date: {
      type: String,
      default: 'March 13'
    },
    temperature: {
      type: Number,
      default: 23
    },
    scale: {
      type: String,
      default: 'Celcius'
    },
    autoAnimate: {
      type: Boolean,
      default: true
    }
  },
  setup(props) {
    const hover = ref(false);
    const animateSun = ref(props.autoAnimate);
    let animationInterval = null;

    const startAnimation = () => {
      if (props.autoAnimate) {
        animationInterval = setInterval(() => {
          animateSun.value = !animateSun.value;
        }, 2000);
      }
    };

    onMounted(() => {
      startAnimation();
    });

    onBeforeUnmount(() => {
      if (animationInterval) {
        clearInterval(animationInterval);
      }
    });

    return {
      hover,
      animateSun
    };
  }
};
</script>

<style scoped>
.card {
  width: 350px;
  height: 235px;
  position: relative;
  padding: 25px;
  background: radial-gradient(178.94% 106.41% at 26.42% 106.41%, #FFF7B1 0%, rgba(255, 255, 255, 0) 71.88%), #FFFFFF;
  box-shadow: 0px 155px 62px rgba(0, 0, 0, 0.01),
    0px 87px 52px rgba(0, 0, 0, 0.05),
    0px 39px 39px rgba(0, 0, 0, 0.09),
    0px 10px 21px rgba(0, 0, 0, 0.1),
    0px 0px 0px rgba(0, 0, 0, 0.1);
  border-radius: 23px;
  transition: all 0.8s cubic-bezier(0.15, 0.83, 0.66, 1);
  cursor: pointer;
  transform: scale(1);
}

.card:hover {
  transform: scale(1.05);
}

.container {
  width: 250px;
  height: 250px;
  position: absolute;
  right: -35px;
  top: -50px;
  display: flex;
  align-items: center;
  justify-content: center;
  transform: scale(0.7);
}

.cloud {
  width: 250px;
}

.front {
  padding-top: 45px;
  margin-left: 25px;
  display: inline;
  position: absolute;
  z-index: 11;
  animation: clouds 8s infinite;
  animation-timing-function: ease-in-out;
}

.back {
  margin-top: -30px;
  margin-left: 150px;
  z-index: 12;
  animation: clouds 12s infinite;
  animation-timing-function: ease-in-out;
}

.right-front {
  width: 45px;
  height: 45px;
  border-radius: 50% 50% 50% 0%;
  background-color: #4c9beb;
  display: inline-block;
  margin-left: -25px;
  z-index: 5;
}

.left-front {
  width: 65px;
  height: 65px;
  border-radius: 50% 50% 0% 50%;
  background-color: #4c9beb;
  display: inline-block;
  z-index: 5;
}

.right-back {
  width: 50px;
  height: 50px;
  border-radius: 50% 50% 50% 0%;
  background-color: #4c9beb;
  display: inline-block;
  margin-left: -20px;
  z-index: 5;
}

.left-back {
  width: 30px;
  height: 30px;
  border-radius: 50% 50% 0% 50%;
  background-color: #4c9beb;
  display: inline-block;
  z-index: 5;
}

.sun {
  width: 120px;
  height: 120px;
  background: -webkit-linear-gradient(to right, #fcbb04, #fffc00);
  background: linear-gradient(to right, #fcbb04, #fffc00);
  border-radius: 60px;
  display: inline;
  position: absolute;
}

.sunshine {
  animation: sunshines 2s infinite;
}

@keyframes sunshines {
  0% {
    transform: scale(1);
    opacity: 0.6;
  }

  100% {
    transform: scale(1.4);
    opacity: 0;
  }
}

@keyframes clouds {
  0% {
    transform: translateX(15px);
  }

  50% {
    transform: translateX(0px);
  }

  100% {
    transform: translateX(15px);
  }
}

.card-header {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.card-header span:first-child {
  word-break: break-all;
  font-weight: 800;
  font-size: 15px;
  line-height: 135%;
  color: rgba(87, 77, 51, 0.66);
}

.card-header span:last-child {
  font-weight: 700;
  font-size: 15px;
  line-height: 135%;
  color: rgba(87, 77, 51, 0.33);
}

.temp {
  position: absolute;
  left: 25px;
  bottom: 12px;
  font-weight: 700;
  font-size: 64px;
  line-height: 77px;
  color: rgba(87, 77, 51, 1);
}

.temp-scale {
  width: 80px;
  height: 36px;
  position: absolute;
  right: 25px;
  bottom: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.06);
  border-radius: 9px;
}

.temp-scale span {
  font-weight: 700;
  font-size: 13px;
  line-height: 134.49%;
  color: rgba(87, 77, 51, 0.66);
}
</style>

使用
import WeatherCard from './WeatherCard.vue';
      <WeatherCard />