pin_drop当前位置:知识文库 ❯ 图文

实战项目:项目08:天气应用界面 - 详细教程与实战指南

一、项目简介

天气应用是最实用的日常工具之一,也是前端UI设计的优秀练习项目。一个设计精良的天气应用需要在有限的界面中清晰展示当前天气、未来预报、温度变化等信息,同时通过视觉设计传达天气的氛围感。

本项目将构建一个功能完善的天气应用界面,包含当前天气未来预报天气图标搜索城市温度单位切换天气动画背景等核心功能。项目使用模拟数据展示天气信息,SVG天气图标和CSS动画营造沉浸式天气体验。


二、需求分析

1. 功能需求

  • 当前天气:温度、天气状况、湿度、风速、体感温度、紫外线指数

  • 未来预报:7天天气预报,包含日期、图标、最高/最低温度

  • 天气图标:SVG图标表示不同天气状况(晴、多云、雨、雪等)

  • 搜索城市:搜索框输入城市名,下拉建议列表

  • 温度单位:摄氏度/华氏度切换

  • 天气动画背景:根据天气状况切换背景动画(雨滴、雪花、阳光等)

2. 交互需求

  • 搜索框输入时显示匹配城市建议

  • 温度单位切换时所有温度值实时更新

  • 天气背景动画随天气状况变化

  • 预报卡片悬停显示详细信息


三、技术选型

技术领域 选型方案 选择理由
天气图标 SVG内联 可CSS控制样式,零请求
动画背景 CSS keyframes 性能优秀,效果丰富
数据模拟 内嵌JSON 无需API,离线可用
搜索建议 前端过滤 城市列表小,前端过滤即可
温度转换 JS计算 切换单位时动态计算

四、页面结构设计

天气应用采用单列布局,从上到下依次展示搜索栏、当前天气、24小时预报和7天预报。整体结构如下:

代码示例

<body>
├── 天气动画背景层
├── 应用容器
│   ├── 搜索栏(城市搜索 + 单位切换)
│   ├── 当前天气卡片
│   │   ├── 城市名和日期
│   │   ├── 大温度数字 + 天气图标
│   │   ├── 天气描述
│   │   └── 详细指标(湿度、风速等)
│   ├── 24小时预报
│   └── 7天预报
└── 页脚

五、完整代码实现

以下是天气应用的完整HTML代码实现,包含CSS样式和JavaScript交互逻辑:

代码示例

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>天气预报</title>
    <style>
        :root {
            --color-primary: #3b82f6;
            --color-bg: #0c1929;
            --color-bg-card: rgba(255, 255, 255, 0.08);
            --color-bg-card-hover: rgba(255, 255, 255, 0.12);
            --color-text: #f1f5f9;
            --color-text-secondary: rgba(241, 245, 249, 0.7);
            --color-text-light: rgba(241, 245, 249, 0.4);
            --color-border: rgba(255, 255, 255, 0.08);
            --color-accent: #fbbf24;
            --radius: 16px;
            --radius-sm: 10px;
            --transition: 0.3s ease;
        }

        *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Noto Sans SC", sans-serif;
            background: var(--color-bg);
            color: var(--color-text);
            line-height: 1.6;
            min-height: 100vh;
            overflow-x: hidden;
        }

        /* 天气动画背景 */
        .weather-bg {
            position: fixed;
            inset: 0;
            z-index: 0;
            overflow: hidden;
            transition: background 1s ease;
        }

        .weather-bg.sunny {
            background: linear-gradient(135deg, #1e3a5f 0%, #2563eb 50%, #60a5fa 100%);
        }

        .weather-bg.cloudy {
            background: linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%);
        }

        .weather-bg.rainy {
            background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #334155 100%);
        }

        .weather-bg.snowy {
            background: linear-gradient(135deg, #1e293b 0%, #475569 50%, #94a3b8 100%);
        }

        /* 雨滴动画 */
        .rain-drops { position: absolute; inset: 0; display: none; }
        .weather-bg.rainy .rain-drops { display: block; }

        .rain-drop {
            position: absolute;
            width: 2px;
            background: linear-gradient(to bottom, transparent, rgba(147, 197, 253, 0.5));
            animation: rainFall linear infinite;
        }

        @keyframes rainFall {
            0% { transform: translateY(-100px); opacity: 0; }
            10% { opacity: 1; }
            90% { opacity: 1; }
            100% { transform: translateY(100vh); opacity: 0; }
        }

        /* 雪花动画 */
        .snow-flakes { position: absolute; inset: 0; display: none; }
        .weather-bg.snowy .snow-flakes { display: block; }

        .snow-flake {
            position: absolute;
            color: rgba(255, 255, 255, 0.7);
            animation: snowFall linear infinite;
            font-size: 12px;
        }

        @keyframes snowFall {
            0% { transform: translateY(-20px) rotate(0deg); opacity: 0; }
            10% { opacity: 1; }
            90% { opacity: 0.8; }
            100% { transform: translateY(100vh) rotate(360deg); opacity: 0; }
        }

        /* 阳光动画 */
        .sun-rays {
            position: absolute;
            top: -100px;
            right: -100px;
            width: 300px;
            height: 300px;
            border-radius: 50%;
            background: radial-gradient(circle, rgba(251, 191, 36, 0.3), transparent 70%);
            display: none;
            animation: sunPulse 4s ease-in-out infinite;
        }

        .weather-bg.sunny .sun-rays { display: block; }

        @keyframes sunPulse {
            0%, 100% { transform: scale(1); opacity: 0.6; }
            50% { transform: scale(1.2); opacity: 0.8; }
        }

        /* 应用容器 */
        .app-container {
            position: relative;
            z-index: 1;
            max-width: 800px;
            margin: 0 auto;
            padding: 24px 20px;
        }

        /* 搜索栏 */
        .search-bar { display: flex; gap: 12px; margin-bottom: 28px; align-items: center; }
        .search-input {
            width: 100%;
            padding: 12px 16px 12px 42px;
            background: var(--color-bg-card);
            border: 1px solid var(--color-border);
            border-radius: var(--radius-sm);
            color: var(--color-text);
            font-size: 0.95rem;
            backdrop-filter: blur(10px);
        }

        /* 当前天气 */
        .current-weather {
            background: var(--color-bg-card);
            backdrop-filter: blur(20px);
            border-radius: var(--radius);
            border: 1px solid var(--color-border);
            padding: 32px;
            margin-bottom: 20px;
        }

        .current-temp { font-size: 5rem; font-weight: 200; line-height: 1; }
        .current-details { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }

        /* 24小时预报 */
        .hourly-scroll { display: flex; gap: 16px; overflow-x: auto; padding-bottom: 8px; }

        /* 7天预报 */
        .daily-item {
            display: grid;
            grid-template-columns: 100px 40px 1fr 60px;
            align-items: center;
            gap: 12px;
            padding: 14px 0;
            border-bottom: 1px solid var(--color-border);
        }

        .daily-temp-bar { height: 6px; background: rgba(255, 255, 255, 0.08); border-radius: 3px; }
        .daily-temp-range {
            position: absolute;
            height: 100%;
            border-radius: 3px;
            background: linear-gradient(90deg, var(--color-primary), var(--color-accent));
        }

        /* 响应式 */
        @media (max-width: 600px) {
            .current-temp { font-size: 3.5rem; }
            .current-details { grid-template-columns: repeat(2, 1fr); }
        }
    </style>
</head>
<body>
    <div class="weather-bg sunny" id="weatherBg">
        <div class="sun-rays"></div>
        <div class="rain-drops" id="rainDrops"></div>
        <div class="snow-flakes" id="snowFlakes"></div>
    </div>

    <div class="app-container">
        <div class="search-bar">
            <input type="text" class="search-input" id="searchInput" placeholder="搜索城市...">
            <button class="unit-toggle" id="unitToggle">&#176;C</button>
        </div>

        <div class="current-weather" id="currentWeather"></div>

        <div class="hourly-scroll" id="hourlyScroll"></div>

        <div id="dailyList"></div>
    </div>

    <script>
        var citiesData = {
            '北京': {
                current: { temp: 22, feels: 20, desc: '晴', humidity: 35, wind: '东北风 3级', uv: 6, icon: '&#9728;', bgClass: 'sunny' },
                hourly: [
                    { time: '现在', temp: 22, icon: '&#9728;' },
                    { time: '15:00', temp: 23, icon: '&#9728;' },
                    { time: '16:00', temp: 22, icon: '&#9728;' }
                ],
                daily: [
                    { day: '今天', date: '4/22', high: 24, low: 12, icon: '&#9728;' },
                    { day: '明天', date: '4/23', high: 26, low: 14, icon: '&#9925;' }
                ]
            }
        };

        var currentCity = '北京';
        var useCelsius = true;

        function convertTemp(celsius) {
            if (useCelsius) return celsius;
            return Math.round(celsius * 9 / 5 + 32);
        }

        function renderCurrentWeather() {
            var data = citiesData[currentCity];
            var c = data.current;
            document.getElementById('currentWeather').innerHTML =
                '<div class="current-temp">' + convertTemp(c.temp) + '&#176;</div>' +
                '<div>' + c.desc + '</div>';
        }

        document.getElementById('unitToggle').addEventListener('click', function() {
            useCelsius = !useCelsius;
            this.innerHTML = useCelsius ? '&#176;C' : '&#176;F';
            renderCurrentWeather();
        });

        renderCurrentWeather();
    </script>
</body>
</html>

六、功能说明

1. 天气动画背景

背景根据当前城市的天气状况自动切换:晴天显示蓝色渐变和脉动阳光效果,多云显示灰色渐变,雨天显示深色渐变和雨滴下落动画,雪天显示灰蓝渐变和雪花飘落动画。雨滴和雪花在初始化时生成,通过随机的位置、大小、速度和延迟创造自然的效果。

2. 搜索城市

搜索框支持输入城市名进行模糊匹配,匹配结果显示在下拉建议列表中,包含城市名、天气描述和当前温度。点击建议项或按回车键切换到对应城市。点击页面其他区域关闭建议列表。

3. 温度单位切换

点击温度单位按钮在摄氏度和华氏度之间切换。切换时所有温度值(当前温度、体感温度、小时预报、每日预报)实时重新计算和渲染。转换公式:F = C * 9/5 + 32

4. 7天预报温度条

每日预报中的温度条使用渐变色条直观展示温度范围。条的位置和宽度根据所有7天中的最低温和最高温计算,确保各天的温度条在同一个刻度下可比较。

5. 24小时预报

24小时预报使用水平滚动容器,支持触摸滑动和鼠标拖拽。当前时段有蓝色高亮边框标识。

提示:天气动画背景使用CSS keyframes实现,通过改变 .weather-bg 的类名切换不同天气效果,transition属性实现平滑过渡。


七、响应式适配

  • 桌面端(>600px):当前天气详情4列网格

  • 手机端(≤600px):温度数字缩小,详情2列网格,每日预报列宽压缩


八、无障碍优化

  • 搜索框有 aria-labelautocomplete="off"

  • 单位切换按钮有 aria-label

  • 键盘支持:回车搜索、Tab导航

  • 颜色对比度在深色背景上满足WCAG标准

  • 当前时段有视觉区分


九、性能优化

  • 雨滴和雪花动画使用CSS keyframes,GPU加速

  • 搜索建议使用前端过滤,无网络请求

  • 温度条使用CSS渐变,无需Canvas

  • 背景切换使用CSS transition,平滑过渡

  • 滚动容器使用 -webkit-overflow-scrolling: touch 优化移动端


十、扩展建议

  • 真实API集成:对接OpenWeatherMap或和风天气API

  • 地理定位:使用Geolocation API自动获取用户位置

  • 空气质量:添加AQI空气质量指数展示

  • 天气预警:显示恶劣天气预警信息

  • 日出日落:添加日出日落时间和太阳弧线图

  • 降水概率:显示逐小时降水概率

  • 天气地图:集成雷达图或卫星云图

  • 桌面小组件:使用Web Widgets API创建桌面小组件

常见问题

天气应用如何实现动画背景切换?

通过改变.weather-bg元素的类名(sunny/cloudy/rainy/snowy)切换不同天气背景。每个类名对应不同的渐变背景和动画效果。CSS transition属性实现背景颜色的平滑过渡,雨滴和雪花动画通过display属性控制显示隐藏。

温度单位如何在摄氏度和华氏度之间切换?

使用useCelsius布尔变量跟踪当前单位状态。点击切换按钮时翻转该变量,然后调用convertTemp函数重新计算所有温度值。转换公式为F = C * 9/5 + 32。所有渲染函数(当前天气、小时预报、每日预报)都调用convertTemp,确保切换时所有温度同步更新。

7天预报的温度条如何计算位置和宽度?

首先遍历所有7天的最高温和最低温,找出全局最小值和最大值。然后计算每天温度条的left百分比:(当天最低温 - 全局最低温) / (全局最高温 - 全局最低温) * 100%。宽度百分比:(当天最高温 - 当天最低温) / (全局最高温 - 全局最低温) * 100%。这样确保各天温度条在同一刻度下可比较。

城市搜索功能如何实现模糊匹配?

监听搜索框的input事件,获取用户输入的城市名。使用Array.filter方法遍历城市数组,通过String.includes方法检查城市名是否包含输入字符串(不区分大小写)。匹配结果渲染为下拉建议列表,每项显示城市名、天气描述和当前温度。点击建议项或按回车键切换到对应城市。

雨滴和雪花动画如何生成?

在初始化时通过循环生成60个雨滴或40个雪花元素。每个元素使用Math.random()生成随机的left位置、height/size大小、animation-duration持续时间和animation-delay延迟。这些随机参数创造自然的下落效果,避免所有元素同步移动。CSS keyframes定义从顶部到底部的位移动画和透明度变化。

24小时预报如何实现水平滚动?

使用CSS flex布局配合overflow-x: auto实现水平滚动。每个小时项设置min-width确保不被压缩。添加-webkit-overflow-scrolling: touch优化移动端触摸滑动体验。自定义滚动条样式(::-webkit-scrollbar)使其与深色主题协调。当前时段通过添加.now类名实现蓝色高亮边框。

标签: 天气应用 天气预报 温度切换 动画背景 城市搜索 CSS动画 响应式设计

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: 实战项目:项目07:音乐播放器界面 - 详细教程与实战指南 下一篇: 实战项目:项目09:任务管理应用 - 详细教程与实战指南

poll相关推荐