Foundation 分页(完整教程)

Foundation 分页:让数据展示更优雅

在开发 Web 应用时,当数据量逐渐变大,一次性展示所有内容不仅影响页面加载速度,还会让用户感到信息过载。这时候,分页就成为一种非常实用的解决方案。Foundation 是一个功能强大的前端框架,它内置了简洁高效的分页组件,帮助开发者轻松实现数据分页功能。本文将带你从零开始掌握 Foundation 分页的核心用法,结合真实案例,让你在项目中得心应手。

想象一下,你在浏览一个电商网站,商品列表有上千条。如果全部显示在一页上,页面会变得无比漫长,用户根本找不到想要的内容。而分页就像一本厚厚的书,每一页只展示部分内容,通过翻页按钮逐步查看,既清晰又高效。Foundation 分页正是这样一个“翻页工具”,它不仅美观,还具备良好的语义化结构和响应式支持。

Foundation 分页的基本结构

Foundation 的分页组件基于 HTML 的 <nav> 标签构建,通过 class="pagination" 来定义分页区域。它支持多种状态,如当前页、上一页、下一页、省略号等,结构清晰、语义明确。

下面是一个最基础的分页结构示例:

<nav class="pagination" role="navigation" aria-label="分页导航">
  <ul class="pagination-list">
    <li class="pagination-item">
      <a href="#" class="pagination-link" aria-label="上一页">‹</a>
    </li>
    <li class="pagination-item">
      <a href="#" class="pagination-link" aria-label="第一页">1</a>
    </li>
    <li class="pagination-item is-current">
      <span class="pagination-link" aria-current="page">2</span>
    </li>
    <li class="pagination-item">
      <a href="#" class="pagination-link">3</a>
    </li>
    <li class="pagination-item">
      <a href="#" class="pagination-link">4</a>
    </li>
    <li class="pagination-item">
      <a href="#" class="pagination-link" aria-label="下一页">›</a>
    </li>
  </ul>
</nav>

注释说明:

  • nav 标签用于定义分页导航区域,role="navigation" 告诉屏幕阅读器这是一个导航结构。
  • aria-label 提供无障碍访问支持,帮助视障用户理解按钮用途。
  • is-current 类表示当前页,用于视觉高亮。
  • pagination-link 是可点击的链接,span 用于当前页,不能点击。

这个结构看似简单,但它是 Foundation 分页的基石。后续的复杂功能都建立在这个基础上。

实现动态分页:从数据到页面的映射

真实项目中,分页数据通常来自后端 API。我们以一个模拟的博客文章列表为例,展示如何将后端返回的数据动态渲染为 Foundation 分页。

假设后端返回如下 JSON 数据:

{
  "total": 100,
  "page": 2,
  "size": 10,
  "data": [
    {"id": 1, "title": "Vue 3.0 新特性解析"},
    {"id": 2, "title": "Java 8 Stream 详解"},
    {"id": 3, "title": "React Hooks 实战"}
  ]
}

我们需要根据 total(总条数)、page(当前页码)、size(每页数量)来生成分页按钮。

以下是 JavaScript 实现逻辑:

function renderPagination(total, page, size, containerId) {
  const totalPages = Math.ceil(total / size); // 计算总页数
  const container = document.getElementById(containerId);
  const list = document.createElement('ul');
  list.className = 'pagination-list';

  // 上一页按钮
  const prevItem = document.createElement('li');
  prevItem.className = 'pagination-item';
  const prevLink = document.createElement('a');
  prevLink.href = `?page=${page - 1}`;
  prevLink.className = 'pagination-link';
  prevLink.textContent = '‹';
  prevLink.setAttribute('aria-label', '上一页');
  prevItem.appendChild(prevLink);
  list.appendChild(prevItem);

  // 页码按钮
  const start = Math.max(1, page - 2); // 从当前页前两页开始
  const end = Math.min(totalPages, page + 2); // 到当前页后两页结束

  for (let i = start; i <= end; i++) {
    const item = document.createElement('li');
    item.className = 'pagination-item';
    const link = document.createElement('a');
    link.href = `?page=${i}`;
    link.className = 'pagination-link';
    link.textContent = i;

    // 当前页高亮
    if (i === page) {
      link.classList.add('is-current');
      link.setAttribute('aria-current', 'page');
    }

    item.appendChild(link);
    list.appendChild(item);
  }

  // 下一页按钮
  const nextItem = document.createElement('li');
  nextItem.className = 'pagination-item';
  const nextLink = document.createElement('a');
  nextLink.href = `?page=${page + 1}`;
  nextLink.className = 'pagination-link';
  nextLink.textContent = '›';
  nextLink.setAttribute('aria-label', '下一页');
  nextItem.appendChild(nextLink);
  list.appendChild(nextItem);

  // 清空容器并插入新分页
  container.innerHTML = '';
  container.appendChild(list);
}

// 调用函数,渲染分页
renderPagination(100, 2, 10, 'pagination-container');

注释说明:

  • Math.ceil(total / size) 计算总页数,确保即使不能整除也能完整覆盖数据。
  • Math.max(1, page - 2) 保证页码不会小于 1。
  • Math.min(totalPages, page + 2) 防止页码超过最大页数。
  • is-current 类用于高亮当前页,aria-current="page" 提供无障碍支持。

这样,你就能根据真实数据动态生成分页控件,实现真正的“Foundation 分页”功能。

高级样式与响应式处理

Foundation 的分页组件默认支持响应式设计。在小屏幕上,页码按钮会自动压缩,减少空间占用。你还可以通过自定义 CSS 进一步优化视觉效果。

例如,添加以下样式,让分页在移动端更紧凑:

/* 移动端优化 */
@media (max-width: 768px) {
  .pagination {
    font-size: 0.875rem;
  }
  .pagination-list {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
  }
  .pagination-item {
    margin: 0.25rem;
  }
  .pagination-link {
    padding: 0.25rem 0.5rem;
  }
}

此外,Foundation 还支持“省略号”功能,当页码过多时,自动隐藏中间部分,只显示首尾和当前页附近。这在页码超过 10 页时非常实用。

<ul class="pagination-list">
  <li class="pagination-item"><a href="?page=1" class="pagination-link">1</a></li>
  <li class="pagination-item"><a href="?page=2" class="pagination-link">2</a></li>
  <li class="pagination-item is-ellipsis"><span class="pagination-link">...</span></li>
  <li class="pagination-item"><a href="?page=5" class="pagination-link">5</a></li>
  <li class="pagination-item is-current"><span class="pagination-link" aria-current="page">6</span></li>
  <li class="pagination-item"><a href="?page=7" class="pagination-link">7</a></li>
  <li class="pagination-item is-ellipsis"><span class="pagination-link">...</span></li>
  <li class="pagination-item"><a href="?page=10" class="pagination-link">10</a></li>
</ul>

注释说明:

  • is-ellipsis 类用于显示省略号,span 代替 a,表示不可点击。
  • 这种设计在页码过多时有效节省空间,提升用户体验。

与 JavaScript 框架集成:Vue 3.0 示例

如果你使用 Vue 3.0,可以将分页逻辑封装为一个可复用的组件。以下是简化的实现:

<template>
  <nav class="pagination" role="navigation" aria-label="分页导航">
    <ul class="pagination-list">
      <!-- 上一页 -->
      <li class="pagination-item">
        <a
          href="#"
          class="pagination-link"
          @click.prevent="goToPage(currentPage - 1)"
          :disabled="currentPage <= 1"
          :aria-label="'上一页'"
        >
          ‹
        </a>
      </li>

      <!-- 页码 -->
      <li
        v-for="page in pages"
        :key="page"
        class="pagination-item"
      >
        <a
          href="#"
          class="pagination-link"
          :class="{ 'is-current': page === currentPage }"
          @click.prevent="goToPage(page)"
          :aria-current="page === currentPage ? 'page' : null"
        >
          {{ page }}
        </a>
      </li>

      <!-- 下一页 -->
      <li class="pagination-item">
        <a
          href="#"
          class="pagination-link"
          @click.prevent="goToPage(currentPage + 1)"
          :disabled="currentPage >= totalPages"
          :aria-label="'下一页'"
        >
          ›
        </a>
      </li>
    </ul>
  </nav>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  total: { type: Number, required: true },
  page: { type: Number, default: 1 },
  size: { type: Number, default: 10 }
})

const emit = defineEmits(['update:page'])

const totalPages = computed(() => Math.ceil(props.total / props.size))
const currentPage = computed(() => props.page)

// 生成页码数组
const pages = computed(() => {
  const start = Math.max(1, currentPage.value - 2)
  const end = Math.min(totalPages.value, currentPage.value + 2)
  const result = []
  for (let i = start; i <= end; i++) {
    result.push(i)
  }
  return result
})

const goToPage = (page) => {
  if (page < 1 || page > totalPages.value) return
  emit('update:page', page)
}
</script>

注释说明:

  • 使用 v-for 动态渲染页码。
  • :disabled 防止点击无效页码。
  • @click.prevent 阻止默认跳转行为,由 Vue 处理路由更新。
  • 通过 emit 向父组件传递页码变化,实现数据双向绑定。

这个组件可以复用于多个页面,提升开发效率。

实际应用建议与最佳实践

在实际项目中,使用 Foundation 分页时需要注意以下几点:

  • 避免页码过多:通常建议最多显示 5~7 个页码(含当前页),避免视觉混乱。
  • 支持键盘导航:为分页按钮添加 tabindexkeydown 事件,提升可访问性。
  • 结合 AJAX:分页跳转时使用 AJAX 加载数据,避免页面刷新,提升体验。
  • 服务器端分页优先:避免一次性加载全部数据,应在后端实现分页查询。
  • 错误处理:当用户输入非法页码(如 0 或负数)时,应自动跳转到合法页。

此外,分页组件的可访问性(Accessibility)非常重要。确保每个按钮都有 aria-labelaria-current,让屏幕阅读器用户也能顺畅操作。

总结

Foundation 分页不仅是一个 UI 组件,更是一种数据管理策略。它帮助我们把“海量数据”拆解为“可管理的小块”,提升用户体验与系统性能。无论是简单的静态分页,还是复杂的动态数据分页,Foundation 都提供了强大的支持。

从基础结构到动态渲染,再到与现代框架集成,我们一步步揭开了 Foundation 分页的面纱。希望你能在项目中灵活运用它,让数据展示更清晰、更优雅。记住,好的分页不是“把数据切开”,而是“把信息理顺”。