bunny笔记|vue3+Autofit.js构建自适应屏幕的框架(含示例代码)

常规尺寸[满屏]

image.png

缩小尺寸[满屏]

image.png

放大尺寸[满屏]

image.png

缩小尺寸[非全屏]

image.png

放大尺寸[非全屏]

image.png

主要亮点:适配任意尺寸

围绕适配使用Autofit展开,项目介绍:

1. 项目基础结构

package.json - 项目依赖配8置
vite.config.js - Vite 构建配置
index.html - 入口 HTML 文件
src/main.js - Vue 应用入口

2. 自适应工具

src/utils/autofit.js - 屏幕自适应缩放工具类

3. 主大屏组件 (src/App.vue)

顶部标题栏(实时时间显示)
左侧面板:AI 类型分布饼图、实时使用趋势折线图
中间面板:总览数据卡片(总使用人数、今日新增、活跃用户、增长率)、地区分布热力图
右侧面板:AI 类型使用排行柱状图、使用时段分析面积图
底部滚动数据条

4. 图表组件

PieChart.vue - 饼图组件(环形图)
LineChart.vue - 折线图组件(多系列)
BarChart.vue - 柱状图组件(横向)
AreaChart.vue - 面积图组件
MapChart.vue - 地图散点图组件

5. 设计特点

深色主题,渐变背景
玻璃态效果(backdrop-filter)
响应式布局,自适应不同屏幕
实时数据更新(模拟)
动画效果和交互

6.其它

a. 页面包含header、footer以及main-content的左中右布局
b. 非全屏尺寸下,宽度100%,高度支持overflow滚动查阅内容
c. 在足够大的屏幕上自动铺满,在小屏幕上保持原始尺寸,支持滚动查看全部内容,窗口大小变化时自动适配
d. 可滚动条,隐藏滚动条占位,所有内容可见

代码- autofit.js 【自己手动搓的-下面还有应用官方autofit.js的示例】

/**
 * autofit.js - 自适应屏幕缩放
 */
export default class Autofit {
  constructor(options = {}) {
    this.el = options.el || '#app'
    this.dw = options.dw || 1920
    this.dh = options.dh || 1080
    this.resize = options.resize || true
    this.delay = options.delay || 100
    this.transition = options.transition || 0.3
    this.init()
  }

  init() {
    this.setScale()
    if (this.resize) {
      this.bindResize()
    }
  }

  setScale() {
    const target = document.querySelector(this.el)
    if (!target) return

    const w = window.innerWidth
    const h = window.innerHeight
    const scaleX = w / this.dw
    const scaleY = h / this.dh

    // 水平方向始终铺满(使用 scaleX),垂直方向根据缩放后的高度决定
    // 计算水平缩放后的实际高度
    const scaledHeight = this.dh * scaleX
    const isHeightEnough = h >= scaledHeight

    if (isHeightEnough) {
      // 屏幕高度足够显示缩放后的内容,水平和垂直都铺满
      const scale = Math.max(scaleX, scaleY)
      target.style.transform = `scale(${scale})`
      target.style.transformOrigin = 'top left'
      target.style.width = `${this.dw}px`
      target.style.height = `${this.dh}px`
      target.style.position = 'fixed'
      target.style.left = '0'
      target.style.top = '0'
      target.style.overflow = 'hidden'
      target.style.transition = `transform ${this.transition}s`
    } else {
      // 屏幕高度不够时,水平铺满(scaleX),垂直保持原始高度并允许滚动
      target.style.transform = `scaleX(${scaleX})`
      target.style.transformOrigin = 'top left'
      target.style.width = `${this.dw}px`
      target.style.height = `${this.dh}px`
      target.style.position = 'relative'
      target.style.left = 'auto'
      target.style.top = 'auto'
      target.style.overflowX = 'hidden'
      target.style.overflowY = 'auto'
      // 移除过渡效果,避免滚动时出现动画
      target.style.transition = 'none'
    }
  }

  bindResize() {
    let timer = null
    window.addEventListener('resize', () => {
      clearTimeout(timer)
      timer = setTimeout(() => {
        this.setScale()
      }, this.delay)
    })
  }

  destroy() {
    window.removeEventListener('resize', this.setScale)
  }
}

代码- index.html

全局样式部分需要关注,配合autofit的使用

<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>AI类型使用人数统计大屏</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    html,
    body {
      width: 100%;
      height: 100%;
      overflow-x: hidden;
      overflow-y: auto;
      font-family: 'Microsoft YaHei', Arial, sans-serif;
      /* 隐藏滚动条但保留滚动功能 */
      scrollbar-width: none;
      /* Firefox */
      -ms-overflow-style: none;
      /* IE 和 Edge */
    }

    /* 隐藏 Chrome、Safari 和 Opera 的滚动条 */
    html::-webkit-scrollbar,
    body::-webkit-scrollbar {
      display: none;
    }

    #app {
      width: 100%;
      min-height: 100%;
      overflow-x: hidden;
      overflow-y: auto;
      position: relative;
      /* 隐藏滚动条但保留滚动功能 */
      scrollbar-width: none;
      /* Firefox */
      -ms-overflow-style: none;
      /* IE 和 Edge */
    }

    /* 隐藏 Chrome、Safari 和 Opera 的滚动条 */
    #app::-webkit-scrollbar {
      display: none;
    }
  </style>
</head>

<body>
  <div id="app"></div>
  <script type="module" src="/src/main.js"></script>
</body>

</html>

代码- 大屏界面index.vue

首页引用autofit.js

<template>
  <div class="dashboard-container" ref="dashboardRef">
    <!-- 顶部标题栏 -->
    <div class="header">
      <div class="title">
        <h1>AI类型使用人数统计大屏</h1>
        <div class="time">{{ currentTime }}</div>
      </div>
    </div>

    <!-- 主要内容区域 -->
    <div class="main-content">
      <!-- 左侧区域 -->
      <div class="left-panel">
        <!-- AI类型分布饼图 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">📊</span>
            <span>AI类型分布</span>
          </div>
          <PieChart :data="pieData" />
        </div>

        <!-- 实时使用趋势 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">📈</span>
            <span>实时使用趋势</span>
          </div>
          <LineChart :data="lineData" />
        </div>
      </div>

      <!-- 中间区域 -->
      <div class="center-panel">
        <!-- 总览数据 -->
        <div class="overview-box">
          <div class="stat-item">
            <div class="stat-value">{{ totalUsers.toLocaleString() }}</div>
            <div class="stat-label">总使用人数</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ todayUsers.toLocaleString() }}</div>
            <div class="stat-label">今日新增</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ activeUsers.toLocaleString() }}</div>
            <div class="stat-label">活跃用户</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ growthRate }}%</div>
            <div class="stat-label">增长率</div>
          </div>
        </div>

        <!-- 地图热力图 -->
        <div class="chart-box map-box">
          <div class="chart-title">
            <span class="icon">🌍</span>
            <span>地区分布热力图</span>
          </div>
          <MapChart :data="mapData" />
        </div>
      </div>

      <!-- 右侧区域 -->
      <div class="right-panel">
        <!-- 各类型使用人数排行 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">🏆</span>
            <span>AI类型使用排行</span>
          </div>
          <BarChart :data="barData" />
        </div>

        <!-- 使用时段分析 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">⏰</span>
            <span>使用时段分析</span>
          </div>
          <AreaChart :data="areaData" />
        </div>
      </div>
    </div>

    <!-- 底部滚动数据 -->
    <div class="footer">
      <div class="scroll-data">
        <div
          class="scroll-item"
          v-for="(item, index) in scrollData"
          :key="index"
        >
          <span class="ai-type">{{ item.type }}</span>
          <span class="user-count">{{ item.count }}人</span>
          <span class="region">{{ item.region }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick } from "vue";
import Autofit from "./utils/autofit.js";
import PieChart from "./components/PieChart.vue";
import LineChart from "./components/LineChart.vue";
import BarChart from "./components/BarChart.vue";
import AreaChart from "./components/AreaChart.vue";
import MapChart from "./components/MapChart.vue";

const dashboardRef = ref(null);
const currentTime = ref("");
const totalUsers = ref(125680);
const todayUsers = ref(3420);
const activeUsers = ref(8920);
const growthRate = ref(28.5);

// 饼图数据
const pieData = ref([
  { name: "ChatGPT", value: 45230 },
  { name: "Claude", value: 32150 },
  { name: "Midjourney", value: 28420 },
  { name: "Stable Diffusion", value: 19880 },
]);

// 折线图数据
const lineData = ref({
  categories: ["00:00", "04:00", "08:00", "12:00", "16:00", "20:00"],
  series: [
    {
      name: "ChatGPT",
      data: [1200, 800, 3500, 6800, 5200, 4100],
    },
    {
      name: "Claude",
      data: [900, 600, 2800, 5500, 4200, 3300],
    },
    {
      name: "Midjourney",
      data: [700, 500, 2200, 4800, 3800, 2900],
    },
  ],
});

// 柱状图数据
const barData = ref([
  { name: "ChatGPT", value: 45230, color: "#5470c6" },
  { name: "Claude", value: 32150, color: "#91cc75" },
  { name: "Midjourney", value: 28420, color: "#fac858" },
  { name: "Stable Diffusion", value: 19880, color: "#ee6666" },
  { name: "DALL-E", value: 15620, color: "#73c0de" },
  { name: "GPT-4", value: 12450, color: "#3ba272" },
]);

// 面积图数据
const areaData = ref({
  categories: [
    "0-2",
    "2-4",
    "4-6",
    "6-8",
    "8-10",
    "10-12",
    "12-14",
    "14-16",
    "16-18",
    "18-20",
    "20-22",
    "22-24",
  ],
  data: [1200, 800, 1500, 3200, 6800, 8500, 7200, 5800, 5200, 4800, 4100, 2800],
});

// 地图数据
const mapData = ref([
  { name: "北京", value: 15230 },
  { name: "上海", value: 14250 },
  { name: "广东", value: 18200 },
  { name: "浙江", value: 11200 },
  { name: "江苏", value: 9800 },
  { name: "四川", value: 7200 },
  { name: "湖北", value: 6500 },
  { name: "山东", value: 5800 },
]);

// 滚动数据
const scrollData = ref([
  { type: "ChatGPT", count: 45230, region: "北京" },
  { type: "Claude", count: 32150, region: "上海" },
  { type: "Midjourney", count: 28420, region: "广东" },
  { type: "Stable Diffusion", count: 19880, region: "浙江" },
  { type: "DALL-E", count: 15620, region: "江苏" },
  { type: "GPT-4", count: 12450, region: "四川" },
]);

let autofit = null;
let timeInterval = null;
let dataUpdateInterval = null;

// 更新时间
const updateTime = () => {
  const now = new Date();
  currentTime.value = now.toLocaleString("zh-CN", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
};

// 模拟数据更新
const updateData = () => {
  // 更新总用户数
  totalUsers.value += Math.floor(Math.random() * 100);
  todayUsers.value += Math.floor(Math.random() * 50);
  activeUsers.value = Math.floor(totalUsers.value * 0.07);

  // 更新折线图数据
  lineData.value.series.forEach((series) => {
    series.data = series.data.map(() => Math.floor(Math.random() * 8000));
  });
};

onMounted(() => {
  // 等待 DOM 渲染完成后再初始化 autofit
  nextTick(() => {
    // 初始化 autofit
    autofit = new Autofit({
      el: ".dashboard-container",
      dw: 1920,
      dh: 1080,
    });
  });

  // 更新时间
  updateTime();
  timeInterval = setInterval(updateTime, 1000);

  // 更新数据
  dataUpdateInterval = setInterval(updateData, 5000);
});

onUnmounted(() => {
  if (autofit) {
    autofit.destroy();
  }
  if (timeInterval) {
    clearInterval(timeInterval);
  }
  if (dataUpdateInterval) {
    clearInterval(dataUpdateInterval);
  }
});
</script>

<style scoped>
.dashboard-container {
  width: 1920px;
  height: 1080px;
  background: linear-gradient(135deg, #0c1622 0%, #1a2332 50%, #0f1b2e 100%);
  position: relative;
  min-height: 1080px;
  /* 宽度和 overflow 由 autofit.js 动态控制 */
  /* 隐藏滚动条但保留滚动功能 */
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE 和 Edge */
}

/* 隐藏 Chrome、Safari 和 Opera 的滚动条 */
.dashboard-container::-webkit-scrollbar {
  display: none;
}

.header {
  height: 80px;
  background: linear-gradient(
    90deg,
    rgba(21, 101, 192, 0.3) 0%,
    rgba(13, 71, 161, 0.2) 100%
  );
  border-bottom: 2px solid rgba(33, 150, 243, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.title h1 {
  font-size: 42px;
  font-weight: bold;
  background: linear-gradient(90deg, #2196f3, #00bcd4, #4dd0e1);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  text-align: center;
  letter-spacing: 4px;
  text-shadow: 0 0 20px rgba(33, 150, 243, 0.5);
}

.time {
  position: absolute;
  right: 40px;
  top: 50%;
  transform: translateY(-50%);
  color: #00bcd4;
  font-size: 20px;
  font-weight: 500;
}

.main-content {
  display: flex;
  padding: 20px;
  gap: 20px;
  height: calc(100% - 160px);
}

.left-panel,
.right-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.center-panel {
  flex: 1.5;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.chart-box {
  background: rgba(15, 27, 46, 0.6);
  border: 1px solid rgba(33, 150, 243, 0.3);
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  flex: 1;
  display: flex;
  flex-direction: column;
}

.chart-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 15px;
  font-size: 20px;
  font-weight: 600;
  color: #e3f2fd;
  border-left: 4px solid #2196f3;
  padding-left: 10px;
}

.chart-title .icon {
  font-size: 24px;
}

.overview-box {
  display: flex;
  gap: 20px;
  background: rgba(15, 27, 46, 0.6);
  border: 1px solid rgba(33, 150, 243, 0.3);
  border-radius: 8px;
  padding: 30px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
}

.stat-item {
  flex: 1;
  text-align: center;
  padding: 20px;
  background: rgba(33, 150, 243, 0.1);
  border-radius: 8px;
  border: 1px solid rgba(33, 150, 243, 0.2);
  transition: all 0.3s;
}

.stat-item:hover {
  background: rgba(33, 150, 243, 0.2);
  transform: translateY(-5px);
  box-shadow: 0 8px 25px rgba(33, 150, 243, 0.3);
}

.stat-value {
  font-size: 36px;
  font-weight: bold;
  color: #00bcd4;
  margin-bottom: 10px;
  text-shadow: 0 0 10px rgba(0, 188, 212, 0.5);
}

.stat-label {
  font-size: 16px;
  color: #b0bec5;
}

.map-box {
  flex: 1;
}

.footer {
  height: 80px;
  background: rgba(15, 27, 46, 0.8);
  border-top: 2px solid rgba(33, 150, 243, 0.3);
  overflow: hidden;
}

.scroll-data {
  display: flex;
  height: 100%;
  align-items: center;
  animation: scroll 30s linear infinite;
  white-space: nowrap;
}

.scroll-item {
  display: inline-flex;
  align-items: center;
  gap: 30px;
  padding: 0 40px;
  color: #e3f2fd;
  font-size: 16px;
  border-right: 1px solid rgba(33, 150, 243, 0.3);
}

.ai-type {
  color: #00bcd4;
  font-weight: 600;
}

.user-count {
  color: #4caf50;
  font-weight: 500;
}

.region {
  color: #ff9800;
}

@keyframes scroll {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}
</style>

其它模块的代码在此就不提供了,需要源码,可私聊~


接下来,我们来看使用官方提供的autofit.js的示例

相关文档点击链接访问

autofit.js 中文文档 | English

安装autofit
npm i autofit.js@2.0.5

在需要的页面进行引入应用
import autofit from "autofit.js";

初始化

onMounted(() => {
  // 等待 DOM 渲染完成后再初始化 autofit
  nextTick(() => {
    autofit.init({
      el: ".dashboard-container",
      dw: 1920,
      dh: 1080,
      resize: true,
      ignore: [],
      transition: 0.3,
      delay: 100,
    });
  });

销毁

onUnmounted(() => {
  // 销毁 autofit
  autofit.off(".dashboard-container");
});

此处提供完整的index.vue文件,可对比示例一中的的手戳处理

<template>
  <div class="dashboard-container" ref="dashboardRef">
    <!-- 顶部标题栏 -->
    <div class="header">
      <div class="title">
        <h1>AI类型使用人数统计大屏</h1>
        <div class="time">{{ currentTime }}</div>
      </div>
    </div>

    <!-- 主要内容区域 -->
    <div class="main-content">
      <!-- 左侧区域 -->
      <div class="left-panel">
        <!-- AI类型分布饼图 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">📊</span>
            <span>AI类型分布</span>
          </div>
          <PieChart :data="pieData" />
        </div>

        <!-- 实时使用趋势 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">📈</span>
            <span>实时使用趋势</span>
          </div>
          <LineChart :data="lineData" />
        </div>
      </div>

      <!-- 中间区域 -->
      <div class="center-panel">
        <!-- 总览数据 -->
        <div class="overview-box">
          <div class="stat-item">
            <div class="stat-value">{{ totalUsers.toLocaleString() }}</div>
            <div class="stat-label">总使用人数</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ todayUsers.toLocaleString() }}</div>
            <div class="stat-label">今日新增</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ activeUsers.toLocaleString() }}</div>
            <div class="stat-label">活跃用户</div>
          </div>
          <div class="stat-item">
            <div class="stat-value">{{ growthRate }}%</div>
            <div class="stat-label">增长率</div>
          </div>
        </div>

        <!-- 地图热力图 -->
        <div class="chart-box map-box">
          <div class="chart-title">
            <span class="icon">🌍</span>
            <span>地区分布热力图</span>
          </div>
          <MapChart :data="mapData" />
        </div>
      </div>

      <!-- 右侧区域 -->
      <div class="right-panel">
        <!-- 各类型使用人数排行 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">🏆</span>
            <span>AI类型使用排行</span>
          </div>
          <BarChart :data="barData" />
        </div>

        <!-- 使用时段分析 -->
        <div class="chart-box">
          <div class="chart-title">
            <span class="icon">⏰</span>
            <span>使用时段分析</span>
          </div>
          <AreaChart :data="areaData" />
        </div>
      </div>
    </div>

    <!-- 底部滚动数据 -->
    <div class="footer">
      <div class="scroll-data">
        <div
          class="scroll-item"
          v-for="(item, index) in scrollData"
          :key="index"
        >
          <span class="ai-type">{{ item.type }}</span>
          <span class="user-count">{{ item.count }}人</span>
          <span class="region">{{ item.region }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from "vue";
import PieChart from "../components/PieChart.vue";
import LineChart from "../components/LineChart.vue";
import BarChart from "../components/BarChart.vue";
import AreaChart from "../components/AreaChart.vue";
import MapChart from "../components/MapChart.vue";
import {
  initialStats,
  initialPieData,
  initialLineData,
  initialBarData,
  initialAreaData,
  initialMapData,
  initialScrollData,
} from "../data/dashboard";
import type {
  PieDataItem,
  LineChartData,
  BarDataItem,
  AreaChartData,
  MapDataItem,
  ScrollDataItem,
} from "../types/chart";

import autofit from "autofit.js";

const dashboardRef = ref<HTMLDivElement | null>(null);
const currentTime = ref<string>("");
const totalUsers = ref<number>(initialStats.totalUsers);
const todayUsers = ref<number>(initialStats.todayUsers);
const activeUsers = ref<number>(initialStats.activeUsers);
const growthRate = ref<number>(initialStats.growthRate);

// 图表数据
const pieData = ref<PieDataItem[]>(initialPieData);
const lineData = ref<LineChartData>(initialLineData);
const barData = ref<BarDataItem[]>(initialBarData);
const areaData = ref<AreaChartData>(initialAreaData);
const mapData = ref<MapDataItem[]>(initialMapData);
const scrollData = ref<ScrollDataItem[]>(initialScrollData);

let timeInterval: ReturnType<typeof setInterval> | null = null;
let dataUpdateInterval: ReturnType<typeof setInterval> | null = null;

// 更新时间
const updateTime = (): void => {
  const now = new Date();
  currentTime.value = now.toLocaleString("zh-CN", {
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  });
};

// 模拟数据更新
const updateData = (): void => {
  // 更新总用户数
  totalUsers.value += Math.floor(Math.random() * 100);
  todayUsers.value += Math.floor(Math.random() * 50);
  activeUsers.value = Math.floor(totalUsers.value * 0.07);

  // 更新折线图数据
  lineData.value.series.forEach((series) => {
    series.data = series.data.map(() => Math.floor(Math.random() * 8000));
  });
};

onMounted(() => {
  // 等待 DOM 渲染完成后再初始化 autofit
  nextTick(() => {
    // // 初始化 autofit
    // autofit = new Autofit({
    //   el: ".dashboard-container",
    //   dw: 1920,
    //   dh: 1080,
    // });

    autofit.init({
      el: ".dashboard-container",
      dw: 1920,
      dh: 1080,
      resize: true,
      ignore: [],
      transition: 0.3,
      delay: 100,
    });
  });

  // 更新时间
  updateTime();
  timeInterval = setInterval(updateTime, 1000);

  // 更新数据
  dataUpdateInterval = setInterval(updateData, 5000);
});

onUnmounted(() => {
  // 销毁 autofit
  autofit.off(".dashboard-container");

  if (timeInterval) {
    clearInterval(timeInterval);
  }
  if (dataUpdateInterval) {
    clearInterval(dataUpdateInterval);
  }
});
</script>

<style scoped>
.dashboard-container {
  width: 1920px;
  height: 1080px;
  background: linear-gradient(135deg, #0c1622 0%, #1a2332 50%, #0f1b2e 100%);
  position: relative;
  min-height: 1080px;
  /* 宽度和 overflow 由 autofit.js 动态控制 */
  /* 隐藏滚动条但保留滚动功能 */
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* IE 和 Edge */
}

/* 隐藏 Chrome、Safari 和 Opera 的滚动条 */
.dashboard-container::-webkit-scrollbar {
  display: none;
}

.header {
  height: 80px;
  background: linear-gradient(
    90deg,
    rgba(21, 101, 192, 0.3) 0%,
    rgba(13, 71, 161, 0.2) 100%
  );
  border-bottom: 2px solid rgba(33, 150, 243, 0.3);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}

.title h1 {
  font-size: 42px;
  font-weight: bold;
  background: linear-gradient(90deg, #2196f3, #00bcd4, #4dd0e1);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  text-align: center;
  letter-spacing: 4px;
  text-shadow: 0 0 20px rgba(33, 150, 243, 0.5);
}

.time {
  position: absolute;
  right: 40px;
  top: 50%;
  transform: translateY(-50%);
  color: #00bcd4;
  font-size: 20px;
  font-weight: 500;
}

.main-content {
  display: flex;
  padding: 20px;
  gap: 20px;
  height: calc(100% - 160px);
}

.left-panel,
.right-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.center-panel {
  flex: 1.5;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.chart-box {
  background: rgba(15, 27, 46, 0.6);
  border: 1px solid rgba(33, 150, 243, 0.3);
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
  flex: 1;
  display: flex;
  flex-direction: column;
}

.chart-title {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 15px;
  font-size: 20px;
  font-weight: 600;
  color: #e3f2fd;
  border-left: 4px solid #2196f3;
  padding-left: 10px;
}

.chart-title .icon {
  font-size: 24px;
}

.overview-box {
  display: flex;
  gap: 20px;
  background: rgba(15, 27, 46, 0.6);
  border: 1px solid rgba(33, 150, 243, 0.3);
  border-radius: 8px;
  padding: 30px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(10px);
}

.stat-item {
  flex: 1;
  text-align: center;
  padding: 20px;
  background: rgba(33, 150, 243, 0.1);
  border-radius: 8px;
  border: 1px solid rgba(33, 150, 243, 0.2);
  transition: all 0.3s;
}

.stat-item:hover {
  background: rgba(33, 150, 243, 0.2);
  transform: translateY(-5px);
  box-shadow: 0 8px 25px rgba(33, 150, 243, 0.3);
}

.stat-value {
  font-size: 36px;
  font-weight: bold;
  color: #00bcd4;
  margin-bottom: 10px;
  text-shadow: 0 0 10px rgba(0, 188, 212, 0.5);
}

.stat-label {
  font-size: 16px;
  color: #b0bec5;
}

.map-box {
  flex: 1;
}

.footer {
  height: 80px;
  background: rgba(15, 27, 46, 0.8);
  border-top: 2px solid rgba(33, 150, 243, 0.3);
  overflow: hidden;
}

.scroll-data {
  display: flex;
  height: 100%;
  align-items: center;
  animation: scroll 30s linear infinite;
  white-space: nowrap;
}

.scroll-item {
  display: inline-flex;
  align-items: center;
  gap: 30px;
  padding: 0 40px;
  color: #e3f2fd;
  font-size: 16px;
  border-right: 1px solid rgba(33, 150, 243, 0.3);
}

.ai-type {
  color: #00bcd4;
  font-weight: 600;
}

.user-count {
  color: #4caf50;
  font-weight: 500;
}

.region {
  color: #ff9800;
}

@keyframes scroll {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-50%);
  }
}
</style>

非全屏尺寸


image.png

全屏尺寸


image.png

相对于示例一的手戳
有一下优势:

达到了真正的任意尺寸的自适应,无需设定滚动处理等问题。整体代码更加简洁、快捷!!!

继续手戳调整处理也可以实现,但有现成的直接用更香。如果需要定制化特殊处理的话,那还是示例一的手戳更容易去调整和适配。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容