前言

美化之前

美化的时候需要修改网站的源文件、添加样式、js之类的,需要一点基础,可以参考

千万别忘了

在添加完js、css后,一定要记得在_config.butterfly.ymlinject里引用

参考链接

为什么改源码版

之前用的是hexo-butterfly-swiper-lyx插件,它通过JS运行时insertAdjacentHTML注入DOM,存在几个问题:

  1. Swiper版本冲突:插件加载Swiper 5.4.5,但如果inject里还引入了其他版本的Swiper(如swiper-bundle.min.js),PJAX跳转回来后新版会覆盖旧版,导致导航箭头渲染异常
  2. PJAX不友好:运行时注入的DOM在PJAX替换内容时需要额外处理注入时机
  3. 加载慢:依赖多个外部CDN(swiper.min.css、swiper.min.js、swiperstyle.css、swiper_init.js),任何一个CDN挂了就出问题

源码版直接在Pug模板里渲染HTML,构建时就生成好了,不需要运行时注入,更快更稳。

效果

和插件版一样的效果,但hover时会渐显文章描述,点击整个轮播区域即可跳转文章。

修改方案

注意

如果之前安装过hexo-butterfly-swiper-lyx或其他swiper插件,需要先禁用

1. 禁用旧插件

_config.butterfly.yml(或_config.yml)中将swiper插件关闭:

1
2
swiper:
enable: false

2. 新建swiper.pug

新建[blogRoot]/themes/butterfly/layout/includes/swiper.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- const swiperPosts = site.posts.data.filter(p => p.swiper_index).sort((a, b) => a.swiper_index - b.swiper_index)
if swiperPosts.length
.recent-post-item(style='height: auto;width: 100%')
#swiper_container.blog-slider.swiper-container-fade.swiper-container-horizontal
.blog-slider__wrp.swiper-wrapper(style='transition-duration: 0ms;')
each item in swiperPosts
- const coverUrl = item.cover || theme.cover.default_cover[0] || ''
- const descr = item.description || '还不知道怎么描述哦'
- const postPath = url_for(item.path)
if theme.pjax && theme.pjax.enable
.blog-slider__item.swiper-slide(style='background:url(' + coverUrl + ');opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms; cursor: pointer;' onclick='pjax.loadUrl("' + postPath + '");')
.blog-slider__content
span.blog-slider__code= item.date.format('YYYY-MM-DD')
span.blog-slider__title= item.title
.blog-slider__text= descr
else
a.blog-slider__item.swiper-slide(style='background:url(' + coverUrl + ');opacity: 1; transform: translate3d(0px, 0px, 0px); transition-duration: 0ms;' href=postPath)
.blog-slider__content
span.blog-slider__code= item.date.format('YYYY-MM-DD')
span.blog-slider__title= item.title
.blog-slider__text= descr
.blog-slider__pagination.swiper-pagination-clickable.swiper-pagination-bullets
.swiper-button-prev
.swiper-button-next

模板会自动读取文章front-matter中的swiper_index字段,按数字升序排列(小的在前)。

3. 修改首页index.pug

修改[blogRoot]/themes/butterfly/layout/index.pug,在#recent-posts内引入轮播模板:

1
2
3
4
5
6
7
8
extends includes/layout.pug

block content
include ./includes/mixins/post-ui.pug
#recent-posts.recent-posts
+ include ./includes/swiper.pug
+postUI
include includes/pagination.pug

4. 添加swiper_init.js

新建[blogRoot]/source/static/js/swiper_init.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
(() => {
let swiperInstance = null;

const initSwiper = () => {
const container = document.querySelector('.blog-slider');
if (!container) return;

// 确保轮播容器排在 #recent-posts 最前面(magnet 插件会 afterbegin 注入分类卡片)
const wrapper = container.closest('.recent-post-item');
const parent = wrapper && wrapper.parentElement;
if (wrapper && parent && parent.firstElementChild !== wrapper) {
parent.insertBefore(wrapper, parent.firstElementChild);
}

// PJAX 回来时先销毁旧实例,避免重复初始化导致箭头异常
if (swiperInstance && typeof swiperInstance.destroy === 'function') {
swiperInstance.destroy(true, true);
swiperInstance = null;
}

swiperInstance = new Swiper('.blog-slider', {
passiveListeners: true,
spaceBetween: 30,
effect: 'fade',
loop: true,
autoplay: {
disableOnInteraction: false,
delay: 3000
},
mousewheel: true,
pagination: {
el: '.blog-slider__pagination',
clickable: true,
},
navigation: {
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev"
}
});

if (swiperInstance.autoplay) {
container.onmouseenter = () => swiperInstance.autoplay.stop();
container.onmouseleave = () => swiperInstance.autoplay.start();
}

// 阻止导航箭头和分页圆点的点击冒泡到 slide(避免触发跳转)
container.querySelectorAll('.swiper-button-prev, .swiper-button-next, .blog-slider__pagination').forEach(el => {
el.addEventListener('click', e => e.stopPropagation());
});
};

document.addEventListener('DOMContentLoaded', initSwiper);
document.addEventListener('pjax:complete', initSwiper);
})();
关键点说明
  • insertBefore:如果你同时使用了hexo-magnet分类磁贴插件,它会通过afterbegin把分类卡片插到#recent-posts最前面,这段代码确保轮播始终排在第一位
  • destroy:PJAX跳转回来时先销毁旧Swiper实例再重建,避免箭头和分页异常
  • stopPropagation:整个slide可点击跳转,但箭头和分页圆点的点击不应触发跳转

5. 添加swiper.css

新建CSS文件(我放在[blogRoot]/themes/butterfly/source/css/_custom/下,会被自动引入)

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
/* 轮播外层容器:裁掉圆角处溢出,避免 hover border 四角露白 */
.recent-post-item:has(#swiper_container) {
overflow: hidden;
}

div#swiper_container {
opacity: 1 !important;
width: 100%;
height: 100%;
position: relative;
transition: all .3s;
}

.blog-slider__pagination {
position: absolute;
bottom: 0 !important;
z-index: 21;
text-align: center
}

.blog-slider__pagination .swiper-pagination-bullet {
margin: 0 8px;
width: 11px;
height: 11px;
display: inline-block;
border-radius: 99px;
background: #fff;
opacity: .8;
transition: all .3s
}

.blog-slider__pagination .swiper-pagination-bullet-active {
opacity: 1;
background: var(--btn-bg);
width: 30px
}

.blog-slider__item {
background-size: cover !important;
background-position: center !important;
cursor: pointer;
}

.blog-slider__item::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .3);
z-index: -1;
left: 0;
top: 0;
}

.blog-slider__content {
padding: 0 50px 20px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
height: 100%
}

span.blog-slider__title {
font-size: 1.5rem
}

.blog-slider__code {
margin-bottom: 0;
display: block;
font-weight: 500
}

/* hover 时渐显文章描述 */
.blog-slider__text {
font-size: 18px;
opacity: 0;
max-height: 0;
overflow: hidden;
transition: opacity .3s ease, max-height .3s ease;
}

.blog-slider__item:hover .blog-slider__text {
opacity: 1;
max-height: 4em;
}

.blog-slider__content > * {
text-align: center;
line-height: 1.5;
margin: 2px 0;
color: #fff
}

/* 导航箭头 */
.swiper-button-prev,
.swiper-button-next {
width: 44px;
height: 44px;
left: 6px;
transition: background .3s;
background-image: none !important;
background: transparent;
border-radius: 50%;
cursor: pointer;
}

.swiper-button-next:hover,
.swiper-button-prev:hover {
background: rgba(255, 255, 255, .3);
}

.swiper-button-next:after,
.swiper-button-prev:after {
font-size: 1.5rem;
color: var(--june) !important;
}

.swiper-button-next,
.swiper-rtl .swiper-button-prev {
right: 6px;
left: auto
}

/* 响应式 */
@media screen and (min-width: 768px) {
#swiper_container {
height: 270px !important
}

.blog-slider__title {
font-size: 1.8rem;
text-align: center
}

.blog-slider__content > * {
margin: 5px !important;
}
}

@media screen and (max-width: 768px) {
#swiper_container {
height: 12rem !important
}

span.blog-slider__title {
font-size: 1.3rem;
text-align: center
}

.blog-slider__content {
padding: 0 40px
}

.blog-slider__text {
display: none
}
}

6. 添加Swiper核心CSS

需要Swiper 5.4.5的核心CSS。可以从CDN下载保存到本地,或者直接在inject.head中引入:

1
2
3
inject:
head:
- <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/Swiper/5.4.5/css/swiper.min.css" media="print" onload="this.media='all'">
推荐

如果你的CSS也是通过Stylus自动引入的(@import '_custom/**/*.css'),可以直接把swiper.min.css下载到_custom目录下,就不需要在inject里引入了

7. 引入inject

_config.butterfly.ymlinject.bottom中引入Swiper JS和初始化脚本:

1
2
3
4
5
inject:
bottom:
# Swiper 5.4.5 核心库(必须在使用 Swiper 的脚本之前加载)
- <script src="https://cdn.bootcdn.net/ajax/libs/Swiper/5.4.5/js/swiper.min.js"></script>
- <script data-pjax type="text/javascript" src="/static/js/swiper_init.js"></script>
加载顺序

swiper.min.js必须排在所有使用new Swiper()的脚本之前,包括essay.jsbaiduhistory.js

8. 文章配置

在需要展示到轮播的文章front-matter中添加swiper_index字段:

1
2
3
4
---
title: 文章标题
swiper_index: 1
---

数字越小越靠前,建议从1开始按顺序编号。

与插件版的区别

对比项 插件版 源码版
HTML生成方式 JS运行时注入 Pug模板构建时渲染
PJAX兼容性 需要处理注入时机 HTML已在页面中,只需重新初始化
CDN依赖 4个外部CDN 1个(仅Swiper核心JS)
版本冲突风险 高(多个Swiper版本共存) 低(统一一个版本)
可定制性 需要改node_modules 直接改Pug/CSS/JS