Hexo 友链页面 - 增加博客社交属性
想让你的博客更有社交属性?想和其他博主互相交流?友链页面是必不可少的!本教程教你如何在 Hexo + Butterfly 主题中实现一个完整的友链功能。
📋 目录
- 什么是友链
- 为什么需要友链
- 实现步骤
- 功能特点
- 使用说明
- 总结
什么是友链
1. 定义
友情链接(Friend Links)是博客之间互相推荐的链接,通常展示在专门的友链页面上。
2. 展示形式
友链通常以卡片形式展示,包含:
- 🖼️ 网站头像:网站的 Logo 或代表图片
- 📝 网站名称:网站的标题
- 🔗 网站链接:网站的 URL
- 💬 网站描述:一句话介绍
3. 作用
- 互相推广:增加网站曝光度
- 建立联系:与其他博主交流
- 提升 SEO:增加外链权重
- 丰富内容:让博客更有人情味
为什么需要友链
1. 社交属性
问题:博客是孤立的,缺乏与其他博主的联系
解决:通过友链建立博客圈子,互相交流学习
2. 流量互换
问题:新博客流量少,难以被发现
解决:通过友链互相推荐,增加访问量
3. SEO 优化
问题:搜索引擎收录慢,排名低
解决:友链提供外链,提升网站权重
4. 内容发现
问题:不知道有哪些优质博客
解决:通过友链发现同类型的优质内容
实现步骤
第一步:创建友链页面
使用 Hexo 命令创建友链页面:
编辑 source/link/index.md,设置页面类型:
1 2 3 4 5 6 7 8 9 10
| --- title: 友情链接 date: 2026-01-07 22:18:43 type: link comments: true ---
<div id="link-container"> <div class="link-loading">加载中...</div> </div>
|
第二步:创建友链数据文件
在 source/_data/ 目录下创建 links.yml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| - name: Hexo link: https://hexo.io/zh-cn/ avatar: https://hexo.io/icon/favicon-196x196.png descr: 快速、简洁且高效的博客框架
- name: Butterfly link: https://butterfly.js.org/ avatar: https://butterfly.js.org/img/avatar.png descr: 一个基于 Hexo 的 Material Design 风格主题
- name: GitHub link: https://github.com/ avatar: https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png descr: 全球最大的代码托管平台
|
字段说明:
name:网站名称
link:网站链接
avatar:头像链接
descr:网站描述
第三步:创建数据生成器
在 scripts/ 目录下创建 link-generator.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| const yaml = require('js-yaml'); const fs = require('fs'); const path = require('path');
hexo.extend.generator.register('link', function(locals) { const linkPath = path.join(hexo.source_dir, '_data', 'links.yml');
if (!fs.existsSync(linkPath)) { return []; }
try { const linkData = yaml.load(fs.readFileSync(linkPath, 'utf8'));
return [{ path: 'links.json', data: JSON.stringify(linkData || []) }]; } catch (error) { console.error('生成友链数据失败:', error); return []; } });
|
作用:将 YAML 格式的友链数据转换为 JSON,供前端加载。
第四步:创建样式文件
在 source/css/ 目录下创建 link.css:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198
| #link-container { margin: 20px 0; }
.link-notice { background: var(--card-bg); border-radius: 12px; padding: 20px; margin-bottom: 20px; border-left: 4px solid #49b1f5; }
.link-notice h3 { margin-top: 0; color: #49b1f5; }
.link-notice code { background: rgba(73, 177, 245, 0.1); padding: 2px 6px; border-radius: 4px; color: #49b1f5; font-weight: bold; }
.link-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; margin-top: 20px; }
.link-card { display: flex; align-items: center; background: var(--card-bg); border-radius: 12px; padding: 20px; text-decoration: none; color: var(--font-color); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; position: relative; overflow: hidden; animation: fadeInUp 0.6s ease-out; opacity: 0; animation-fill-mode: forwards; }
.link-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: linear-gradient(90deg, #49b1f5, #ff7242); transform: scaleX(0); transition: transform 0.3s ease; }
.link-card:hover::before { transform: scaleX(1); }
.link-card:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15); }
.link-avatar { width: 60px; height: 60px; border-radius: 50%; object-fit: cover; margin-right: 15px; border: 3px solid transparent; transition: all 0.3s ease; }
.link-card:hover .link-avatar { transform: rotate(360deg); border-color: #49b1f5; }
.link-info { flex: 1; min-width: 0; }
.link-name { font-size: 18px; font-weight: bold; margin-bottom: 5px; color: var(--font-color); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.link-descr { font-size: 14px; color: var(--font-color); opacity: 0.7; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.link-loading { text-align: center; padding: 40px; color: var(--font-color); opacity: 0.7; }
.link-empty { text-align: center; padding: 40px; color: var(--font-color); opacity: 0.5; }
.link-error { text-align: center; padding: 40px; color: #ff7242; }
.link-error i { font-size: 48px; margin-bottom: 10px; }
@keyframes fadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
@media (max-width: 768px) { .link-grid { grid-template-columns: 1fr; gap: 15px; }
.link-card { padding: 15px; }
.link-avatar { width: 50px; height: 50px; }
.link-name { font-size: 16px; }
.link-descr { font-size: 13px; } }
[data-theme="dark"] .link-card { background: #1f1f1f; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); }
[data-theme="dark"] .link-card:hover { box-shadow: 0 8px 20px rgba(0, 0, 0, 0.5); }
[data-theme="dark"] .link-notice { background: #1f1f1f; }
|
特点:
- ✅ Grid 网格布局,自适应列数
- ✅ 卡片悬停效果(上移、阴影、头像旋转)
- ✅ 渐变顶部边框动画
- ✅ 淡入动画,错开延迟
- ✅ 响应式设计,移动端友好
- ✅ 深色模式适配
第五步:创建 JavaScript 文件
在 source/js/ 目录下创建 link.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
| function initLink() { const container = document.getElementById('link-container'); if (container) { loadLinks(); } }
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initLink); } else { initLink(); }
document.addEventListener('pjax:complete', initLink);
function loadLinks() { fetch('/links.json') .then(response => response.json()) .then(data => { renderLinks(data); }) .catch(error => { console.error('加载友链数据失败:', error); showLinkError(); }); }
function renderLinks(linkList) { const container = document.getElementById('link-container');
if (!container) { console.error('找不到友链容器'); return; }
if (!linkList || linkList.length === 0) { container.innerHTML = '<div class="link-empty">暂无友链</div>'; return; }
let html = ` <div class="link-notice"> <h3>🔗 友链说明</h3> <p>欢迎交换友链!请在评论区留言,格式如下:</p> <p> <code>名称</code>:你的网站名称<br> <code>链接</code>:你的网站地址<br> <code>头像</code>:你的头像链接<br> <code>描述</code>:一句话介绍 </p> <p>💡 本站信息:</p> <p> <code>名称</code>:cyforkk<br> <code>链接</code>:https://cyforkk.top/<br> <code>头像</code>:https://cyforkk.top/images/wallpaper-img/sanye.png<br> <code>描述</code>:找寻自我 </p> </div> <div class="link-grid"> `;
linkList.forEach((item, index) => { const name = escapeHtml(item.name); const link = escapeHtml(item.link); const avatar = escapeHtml(item.avatar); const descr = escapeHtml(item.descr);
html += ` <a href="${link}" class="link-card" target="_blank" rel="noopener" style="animation-delay: ${index * 0.1}s"> <img src="${avatar}" alt="${name}" class="link-avatar" onerror="this.src='/img/friend_404.gif'"> <div class="link-info"> <div class="link-name">${name}</div> <div class="link-descr">${descr}</div> </div> </a> `; });
html += '</div>'; container.innerHTML = html; }
function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; }
function showLinkError() { const container = document.getElementById('link-container'); if (container) { container.innerHTML = ` <div class="link-error"> <i class="fas fa-exclamation-triangle"></i> <p>加载友链失败,请稍后再试</p> </div> `; } }
|
功能:
- ✅ 从
/links.json 加载数据
- ✅ 动态渲染友链卡片
- ✅ HTML 转义防止 XSS 攻击
- ✅ 错误处理和友好提示
- ✅ PJAX 兼容性
- ✅ 图片加载失败处理
第六步:注入 CSS 和 JS
编辑 _config.butterfly.yml,在 inject 部分添加:
1 2 3 4 5
| inject: head: - <link rel="stylesheet" href="/css/link.css"> bottom: - <script src="/js/link.js"></script>
|
第七步:添加导航菜单
在 _config.butterfly.yml 的 menu 部分添加:
1 2
| menu: 友链: /link/ || fas fa-link
|
第八步:生成并部署
1 2 3 4 5 6 7 8 9 10 11
| hexo clean
hexo generate
hexo server
hexo deploy
|
功能特点
✨ 核心功能
数据管理
- YAML 格式,易于编辑
- 自动转换为 JSON
- 支持批量添加
美观界面
交互体验
- 悬停效果(上移、阴影、旋转)
- 渐变边框动画
- 错开淡入动画
响应式设计
深色模式
安全性
兼容性
使用说明
添加友链
编辑 source/_data/links.yml,添加新的友链:
1 2 3 4
| - name: 你的网站名称 link: https://your-website.com/ avatar: https://your-website.com/avatar.png descr: 你的网站描述
|
修改友链
直接编辑 links.yml 文件,修改对应字段即可。
删除友链
从 links.yml 文件中删除对应的友链条目。
自定义样式
编辑 source/css/link.css,修改样式变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .link-notice { border-left: 4px solid #your-color; }
.link-grid { gap: 30px; }
.link-card { border-radius: 16px; }
|
自定义说明
编辑 source/js/link.js,修改友链说明部分:
1 2 3 4 5 6 7
| <p> <code>名称</code>:你的网站名称<br> <code>链接</code>:你的网站地址<br> <code>头像</code>:你的头像链接<br> <code>描述</code>:你的网站描述 </p>
|
总结
通过本教程,我们实现了一个完整的友链功能,包括:
✅ 已实现功能
- 数据管理:YAML 格式,易于维护
- 自动生成:YAML 转 JSON,无需手动处理
- 美观界面:卡片式设计,Grid 布局
- 动画效果:悬停、淡入、旋转等
- 响应式:自适应不同屏幕尺寸
- 深色模式:自动适配主题
- 安全性:HTML 转义,XSS 防护
- 兼容性:PJAX 支持,错误处理
📊 效果展示
访问 http://localhost:4000/link/ 查看效果:
- 🎨 精美的卡片式布局
- ✨ 流畅的动画效果
- 📱 完美的移动端适配
- 🌙 深色模式支持
- 🔒 安全的数据处理
🎯 下一步
- 添加更多友链:与其他博主交换友链
- 优化样式:根据个人喜好调整设计
- 增加功能:如友链分类、搜索等
- 定期维护:检查友链有效性
💡 提示
- 定期检查友链是否有效
- 与友链博主保持联系
- 优先添加活跃的博客
- 注意友链的质量而非数量
相关文章:
参考资源: