学习目标
- 掌握使用Tailwind CSS实现GitHub代码查看页的整体布局
- 学习如何构建代码查看界面,包括语法高亮和行号显示
- 实现文件头部信息区域,包括文件路径和操作按钮
- 理解如何使用Tailwind创建交互式代码行号和行高亮功能
- 学习实现响应式设计,确保页面在不同设备上的良好表现
页面分析
GitHub代码查看页是开发者浏览和查看仓库代码的核心界面,它包含以下主要区域:
- 顶部导航栏 - 与仓库详情页相同,包含GitHub logo、搜索框和用户菜单
- 仓库信息头部 - 显示仓库名称和基本信息
- 仓库导航菜单 - 提供Code、Issues、PR等功能入口
- 文件路径导航 - 显示当前文件的目录路径
- 文件操作栏 - 包含各种文件操作按钮(查看原始文件、编辑、删除等)
- 代码查看区 - 显示代码内容,包括行号和语法高亮
- 文件底部 - 可能包含提交信息或其他元数据
在本节中,我们将重点关注文件路径导航、文件操作栏以及代码查看区域的实现,因为这些部分是代码查看页面的核心特色。
实现步骤
第1步:准备HTML骨架与Tailwind配置
首先创建一个基础HTML文件,并引入Tailwind CSS和必要的配置:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>GitHub 代码查看页 - Tailwind CSS 复刻</title><!-- 引入Tailwind CSS --><script src="https://cdn.tailwindcss.com"></script><!-- 配置Tailwind为GitHub风格 --><script>tailwind.config = {theme: {extend: {colors: {'github-blue': '#0969da','github-green': '#2da44e','github-red': '#cf222e','github-yellow': '#bf8700','github-purple': '#8250df','github-bg': '#f6f8fa','github-border': '#d0d7de','github-text': '#24292f','github-text-secondary': '#57606a','github-line-selected': '#fff8c5','github-line-highlight': '#fffbdd','github-diff-add': '#e6ffec','github-diff-delete': '#ffebe9',},spacing: {'4.5': '1.125rem',}},},}</script><!-- 添加Prism.js用于代码高亮 --><link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism.min.css" rel="stylesheet" /><script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/prism.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/components/prism-javascript.min.js"></script><style>/* 自定义样式以匹配GitHub的代码高亮 */.token.comment { color: #6e7781; }.token.keyword { color: #cf222e; }.token.string { color: #0a3069; }.token.function { color: #8250df; }.token.number { color: #0550ae; }.token.operator { color: #24292f; }.token.class-name { color: #953800; }pre[class*="language-"] {margin: 0;padding: 0;background: transparent;}code[class*="language-"] {background: transparent;padding: 0;border-radius: 0;white-space: pre;word-break: normal;font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace;}/* 隐藏Prism行号,我们将自己实现 */.line-numbers .line-numbers-rows {display: none;}</style></head><body class="bg-white text-github-text"><!-- 页面内容将在这里构建 --></body></html>
我们增加了与代码高亮相关的颜色配置,并引入了Prism.js库来帮助我们实现语法高亮。同时添加了自定义CSS来匹配GitHub的代码高亮风格。
注意:通常在实际项目中,我们会通过配置Tailwind来实现自定义样式,但为了简化演示,这里直接使用了内联样式。
第2步:实现文件路径导航
文件路径导航显示当前文件在仓库中的位置:
<!-- 文件路径导航 --><div class="bg-white border-b border-github-border"><div class="container mx-auto px-4 sm:px-6 lg:px-8 py-3"><div class="flex flex-wrap items-center text-sm"><div class="flex items-center"><a href="#" class="text-github-blue hover:underline">repository-name</a><span class="mx-1 text-github-text-secondary">/</span></div><!-- 中间目录 --><div class="flex items-center"><a href="#" class="text-github-blue hover:underline">src</a><span class="mx-1 text-github-text-secondary">/</span></div><div class="flex items-center"><a href="#" class="text-github-blue hover:underline">components</a><span class="mx-1 text-github-text-secondary">/</span></div><!-- 当前文件 --><div class="flex items-center font-semibold"><span>Button.js</span></div></div></div></div>
这个路径导航设计得非常简洁直观,使用斜杠分隔每个目录层级,并将当前文件加粗显示。
第3步:实现文件操作栏
文件操作栏包含各种文件操作按钮和文件信息:
<!-- 文件操作栏 --><div class="bg-github-bg border-b border-github-border"><div class="container mx-auto px-4 sm:px-6 lg:px-8 py-2"><div class="flex flex-col lg:flex-row lg:items-center justify-between"><!-- 左侧:文件信息和操作按钮 --><div class="flex items-center flex-wrap mb-2 lg:mb-0"><!-- 行数和大小信息 --><div class="flex items-center text-sm text-github-text-secondary mr-4"><span>78 lines (64 sloc)</span><span class="mx-1">·</span><span>2.3 KB</span></div><!-- 操作按钮组 --><div class="flex items-center space-x-2"><!-- 编辑按钮 --><button class="flex items-center text-xs bg-github-bg border border-github-border rounded-md px-2 py-1 hover:bg-gray-100"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" /></svg>Edit</button><!-- 原始文件按钮 --><button class="flex items-center text-xs bg-github-bg border border-github-border rounded-md px-2 py-1 hover:bg-gray-100"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" /></svg>Raw</button><!-- 复制按钮 --><button class="flex items-center text-xs bg-github-bg border border-github-border rounded-md px-2 py-1 hover:bg-gray-100"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75" /></svg>Copy</button></div></div><!-- 右侧:Git信息和更多操作 --><div class="flex items-center flex-wrap"><!-- Git信息 --><div class="flex items-center text-xs mr-4"><a href="#" class="flex items-center text-github-text-secondary hover:text-github-blue"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" /></svg><span>Latest commit</span><span class="ml-1 font-mono text-github-blue">a2b9c8d</span><span class="ml-1">on</span><span class="ml-1 text-github-blue">Mar 15, 2023</span></a></div><!-- 历史按钮 --><a href="#" class="flex items-center text-xs text-github-text-secondary hover:text-github-blue mr-4"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" /></svg><span>History</span></a><!-- 责任按钮 --><a href="#" class="flex items-center text-xs text-github-text-secondary hover:text-github-blue"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4 mr-1"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19.128a9.38 9.38 0 0 0 2.625.372 9.337 9.337 0 0 0 4.121-.952 4.125 4.125 0 0 0-7.533-2.493M15 19.128v-.003c0-1.113-.285-2.16-.786-3.07M15 19.128v.106A12.318 12.318 0 0 1 8.624 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" /></svg><span>Blame</span></a></div></div></div></div>
文件操作栏分为左右两部分:左侧显示文件基本信息和常用操作按钮,右侧显示Git相关信息和其他操作链接。在移动设备上,它们会垂直堆叠以提供更好的可读性。
第4步:实现代码查看区
代码查看区是本页面的核心部分,包括行号和语法高亮的代码内容:
<!-- 代码查看区 --><div class="container mx-auto px-4 sm:px-6 lg:px-8 py-4"><div class="border border-github-border rounded-md overflow-hidden"><!-- 文件语言栏 --><div class="bg-github-bg border-b border-github-border px-4 py-2 flex justify-between items-center"><div class="text-sm font-medium">JavaScript</div><div><button class="text-github-text-secondary hover:text-github-text"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" /></svg></button></div></div><!-- 代码内容 --><div class="overflow-x-auto"><table class="w-full border-collapse"><tbody><!-- 代码行 --><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L1"><a href="#L1" class="block py-1">1</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">import React from 'react';</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L2"><a href="#L2" class="block py-1">2</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">import PropTypes from 'prop-types';</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L3"><a href="#L3" class="block py-1">3</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr class="bg-github-line-highlight"><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-github-line-highlight border-r border-github-border" id="L4"><a href="#L4" class="block py-1">4</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">/**</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L5"><a href="#L5" class="block py-1">5</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> * Button component for primary actions in forms and dialogs.</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L6"><a href="#L6" class="block py-1">6</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> */</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L7"><a href="#L7" class="block py-1">7</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">const Button = ({ children, variant, size, disabled, onClick }) => {</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L8"><a href="#L8" class="block py-1">8</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> // Base classes always applied</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L9"><a href="#L9" class="block py-1">9</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> const baseClasses = 'inline-flex items-center justify-center rounded-md font-medium focus:outline-none focus:ring-2 focus:ring-offset-2';</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L10"><a href="#L10" class="block py-1">10</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L11"><a href="#L11" class="block py-1">11</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> // Variant specific classes</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L12"><a href="#L12" class="block py-1">12</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> const variantClasses = {</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L13"><a href="#L13" class="block py-1">13</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L14"><a href="#L14" class="block py-1">14</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L15"><a href="#L15" class="block py-1">15</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',</code></div></td></tr><!-- 更多代码行... --></tbody></table></div></div></div>
代码查看区使用了表格布局,左侧是行号,右侧是代码内容。表格的每一行代表代码的一行,并使用Prism.js进行语法高亮。
为了展示行高亮功能,我们对第4行添加了高亮背景色。在实际应用中,通常会根据URL中的锚点来确定哪一行需要高亮显示。
第5步:添加更多代码行和文件底部信息
为了更完整地展示代码文件,我们添加更多代码行,并在底部添加文件信息:
<!-- 更多代码行... --><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L16"><a href="#L16" class="block py-1">16</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> };</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L17"><a href="#L17" class="block py-1">17</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L16"><a href="#L16" class="block py-1">16</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> };</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L17"><a href="#L17" class="block py-1">17</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L18"><a href="#L18" class="block py-1">18</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> // Size specific classes</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L19"><a href="#L19" class="block py-1">19</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> const sizeClasses = {</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L20"><a href="#L20" class="block py-1">20</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> small: 'px-3 py-1.5 text-sm',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L21"><a href="#L21" class="block py-1">21</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> medium: 'px-4 py-2 text-base',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L22"><a href="#L22" class="block py-1">22</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> large: 'px-5 py-2.5 text-lg',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L23"><a href="#L23" class="block py-1">23</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> };</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L24"><a href="#L24" class="block py-1">24</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L25"><a href="#L25" class="block py-1">25</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> // Combine all classes</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L26"><a href="#L26" class="block py-1">26</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> const classes = [</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L27"><a href="#L27" class="block py-1">27</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> baseClasses,</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L28"><a href="#L28" class="block py-1">28</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> variantClasses[variant],</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L29"><a href="#L29" class="block py-1">29</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> sizeClasses[size],</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L30"><a href="#L30" class="block py-1">30</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L31"><a href="#L31" class="block py-1">31</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> ].join(' ');</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L32"><a href="#L32" class="block py-1">32</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L33"><a href="#L33" class="block py-1">33</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> return (</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L34"><a href="#L34" class="block py-1">34</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> <button</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L35"><a href="#L35" class="block py-1">35</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> className={classes}</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L36"><a href="#L36" class="block py-1">36</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> disabled={disabled}</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L37"><a href="#L37" class="block py-1">37</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> onClick={onClick}</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L38"><a href="#L38" class="block py-1">38</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> ></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L39"><a href="#L39" class="block py-1">39</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> {children}</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L40"><a href="#L40" class="block py-1">40</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> </button></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L41"><a href="#L41" class="block py-1">41</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> );</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L42"><a href="#L42" class="block py-1">42</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">};</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L43"><a href="#L43" class="block py-1">43</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L44"><a href="#L44" class="block py-1">44</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">// Default props</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L45"><a href="#L45" class="block py-1">45</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">Button.defaultProps = {</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L46"><a href="#L46" class="block py-1">46</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> variant: 'primary',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L47"><a href="#L47" class="block py-1">47</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> size: 'medium',</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L48"><a href="#L48" class="block py-1">48</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> disabled: false,</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L49"><a href="#L49" class="block py-1">49</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> onClick: () => {},</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L50"><a href="#L50" class="block py-1">50</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">};</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L51"><a href="#L51" class="block py-1">51</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L52"><a href="#L52" class="block py-1">52</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">// PropTypes</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L53"><a href="#L53" class="block py-1">53</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">Button.propTypes = {</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L54"><a href="#L54" class="block py-1">54</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> children: PropTypes.node.isRequired,</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L55"><a href="#L55" class="block py-1">55</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> variant: PropTypes.oneOf(['primary', 'secondary', 'danger']),</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L56"><a href="#L56" class="block py-1">56</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> size: PropTypes.oneOf(['small', 'medium', 'large']),</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L57"><a href="#L57" class="block py-1">57</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> disabled: PropTypes.bool,</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L58"><a href="#L58" class="block py-1">58</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"> onClick: PropTypes.func,</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L59"><a href="#L59" class="block py-1">59</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">};</code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L60"><a href="#L60" class="block py-1">60</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript"></code></div></td></tr><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L61"><a href="#L61" class="block py-1">61</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">export default Button;</code></div></td></tr>
第6步:实现文件底部信息
最后,添加文件底部信息:
<!-- 文件底部信息 --><div class="border-t border-github-border py-2 px-4 text-center text-xs text-github-text-secondary"><p>You can't perform that action at this time.</p><p>You signed in with another tab or window. Reload to refresh your session.</p></div>
这个底部信息通常会出现在一些页面状态下,比如用户会话过期或没有权限执行某些操作时。
关键样式技术点解析
1. 代码行与行号的实现
GitHub代码查看页面最核心的部分是代码行与行号的实现,我们使用表格布局来确保行号和代码内容能够保持对齐:
<table class="w-full border-collapse"><tbody><tr><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-white border-r border-github-border" id="L1"><a href="#L1" class="block py-1">1</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">import React from 'react';</code></div></td></tr></tbody></table>
关键样式类解析:
border-collapse: 确保表格边框合并,创建一个连续的视觉效果py-0 pr-2 pl-4: 精确控制每个单元格的内边距text-right select-none: 使行号右对齐且不可选text-github-text-secondary text-xs: 行号使用次要文本颜色和小号字体w-10: 给行号列一个固定宽度border-r border-github-border: 添加右边框分隔行号和代码whitespace-pre: 保留代码中的空格和换行py-1: 为代码行内容提供适当的垂直间距
2. 行高亮实现
GitHub代码查看页支持行高亮功能,当用户点击行号或通过URL锚点导航时:
<tr class="bg-github-line-highlight"><td class="py-0 pr-2 pl-4 text-right select-none text-github-text-secondary text-xs w-10 bg-github-line-highlight border-r border-github-border" id="L4"><a href="#L4" class="block py-1">4</a></td><td class="py-0 pl-4 pr-4 whitespace-pre"><div class="py-1"><code class="language-javascript">/**</code></div></td></tr>
关键样式类:
bg-github-line-highlight: 为整行添加高亮背景色- 同样的颜色应用于行号单元格
bg-github-line-highlight,保持视觉一致性
3. 语法高亮集成
我们使用Prism.js实现代码语法高亮,并自定义样式以匹配GitHub的风格:
/* 自定义样式以匹配GitHub的代码高亮 */.token.comment { color: #6e7781; }.token.keyword { color: #cf222e; }.token.string { color: #0a3069; }.token.function { color: #8250df; }.token.number { color: #0550ae; }.token.operator { color: #24292f; }.token.class-name { color: #953800; }
每种代码元素(注释、关键字、字符串等)都有特定的颜色,精确匹配GitHub的代码高亮风格。
4. 响应式导航与操作按钮
文件操作栏在不同屏幕尺寸下有不同的布局:
<div class="flex flex-col lg:flex-row lg:items-center justify-between"><!-- 左侧内容 --><div class="flex items-center flex-wrap mb-2 lg:mb-0"><!-- 内容 --></div><!-- 右侧内容 --><div class="flex items-center flex-wrap"><!-- 内容 --></div></div>
关键样式类:
flex flex-col lg:flex-row: 在移动设备上垂直堆叠,在大屏幕上水平排列flex-wrap: 允许按钮在空间不足时自动换行mb-2 lg:mb-0: 在移动设备上添加底部边距,在大屏幕上移除
5. 按钮组与链接样式
GitHub代码查看页面有多种按钮和链接样式:
<button class="flex items-center text-xs bg-github-bg border border-github-border rounded-md px-2 py-1 hover:bg-gray-100"><svg class="w-4 h-4 mr-1"><!-- ... --></svg>Edit</button><a href="#" class="flex items-center text-xs text-github-text-secondary hover:text-github-blue"><svg class="w-4 h-4 mr-1"><!-- ... --></svg><span>History</span></a>
按钮样式:
- 浅灰背景 (
bg-github-bg) - 细边框 (
border border-github-border) - 圆角 (
rounded-md) - 小字体 (
text-xs) - 图标+文本组合 (
flex items-center) - 悬停效果 (
hover:bg-gray-100)
链接样式:
- 次要文本颜色 (
text-github-text-secondary) - 悬停时变为蓝色 (
hover:text-github-blue) - 无背景和边框
- 同样的图标+文本布局
常见问题与解决方案
问题: 如何保持代码格式和空格不变
解决方案: 使用whitespace-pre类保留代码中的所有空格和换行,而不是默认的文本换行行为。问题: 行号与代码内容对齐
解决方案: 使用表格布局确保行号与代码内容垂直对齐,即使在代码内容高度变化时也能保持一致。问题: 如何实现行高亮功能
解决方案: 为整个表格行添加背景色类bg-github-line-highlight,并确保行号单元格也使用相同的背景色。问题: 代码超出视口宽度时的处理
解决方案: 使用overflow-x-auto类允许代码区域水平滚动,而不破坏页面整体布局。问题: 语法高亮与GitHub样式不匹配
解决方案: 使用自定义CSS覆盖Prism.js的默认样式,精确匹配GitHub的代码高亮颜色。
扩展与练习建议
练习1:实现代码行选择功能
扩展代码,使用JavaScript实现多行选择功能,当用户点击行号并按住Shift键点击另一行时,高亮显示所选范围内的所有行。
练习2:添加代码注释功能
在特定代码行右侧添加注释图标按钮,点击后弹出注释输入框。
练习3:实现代码折叠功能
为代码添加折叠功能,可以折叠/展开特定的代码块(如函数、类定义等)。
练习4:实现”复制到剪贴板”功能
使用JavaScript实现”复制”按钮的功能,点击后将代码复制到剪贴板,并显示成功提示。
练习5:添加行内代码建议功能
实现类似GitHub Copilot的简单版本,在特定代码行下方显示建议的下一行代码。
总结
在本节中,我们成功实现了GitHub代码查看页的核心界面元素,重点关注代码显示、语法高亮和行号实现。关键实现点包括:
- 使用表格布局实现代码行与行号的精确对齐
- 通过Prism.js实现语法高亮,并自定义样式匹配GitHub的风格
- 实现行高亮功能,支持通过URL锚点定位到特定代码行
- 创建响应式文件操作栏,包含编辑、原始查看和复制等功能
- 精确还原GitHub的交互元素,如按钮、链接和图标组合
通过这些实现,我们不仅展示了Tailwind CSS在复杂UI组件中的应用,也深入理解了GitHub代码查看界面的设计原则。代码查看页面的设计重点在于提供清晰的代码可读性、便捷的导航和实用的代码操作功能。
在下一节中,我们将探讨Commit历史页面的实现,学习如何创建时间线和变更记录视图。
