更新日志

2023/10/18

  • 更新精灵图生成器,修改缺省的本地文件库的位置,修复db.json异常膨胀的 BUG

2023/06/15

  • 窄屏不再添加 css 动画
  • 精灵图支持拆分
  • 改进精灵图生成器

2023/04/23

  • 发布初代版本

  该教程不适合小白食用,需要有一定的基本功!

  教程中没有支持配置项,如果你希望支持配置项的话可以自行修改。

魔改

  首先需要修改 pug,修改\layout\includes\page\flink.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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#article-container
.flink
if site.data.link
-
let index = 0 // 计数器,不要修改
let group = 0 // 分组计数器,不要修改
// 头像文件名,按需修改
const avatarRoot = './sprites/avatar-'
const ext = 'jpg' // 头像拓展名,按需修改
const limit = 0 // 每张图片头像数量限制,按需修改,需要和精灵图保持一致
// 样式表,不要修改
let rootStyle = `.group-0{background:url(${avatarRoot}0.${ext})}`
-
each list in site.data.link
if list.class_name
h2!= list.class_name
if list.class_desc
.flink-desc!= list.class_desc
- let className = list.beautify ? 'beautify' : 'terse'
- if (list.deprecated) className += ' deprecated'
.flink-list(class = className)
each item in list.link_list
a.flink-item(href = url_for(item.link), target = '_blank')
if list.beautify
if item.tag
span.card-tag!= item.tag
img.card-bg.no-lightbox(src = url_for(item.siteshot))
.info
if list.deprecated
.text
span.name!= item.name
else
- const styleStr = index === 0 ? '' : `background-position-y:-${(index) * 100}px`
.avatar.no-lightbox(class=`group-${group.toString(16)}`, style=styleStr)
.text
span.name!= item.name
span.descr!= item.descr
-
if (++index === limit) {
index = 0
++group
rootStyle += `.group-${group.toString(16)}{background:url(${avatarRoot}${group}.${ext})}`
};
-
style!= rootStyle
!= page.content

  然后添加样式,修改\source\css\_page\flink.styl为:

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
maxWidth(value)
@media screen and (max-width: unit(value, px))
{block}

minWidth(value)
@media screen and (min-width: unit(value, px))
{block}

.flink-list
display flex
flex-wrap wrap

.flink-item
position relative
border-radius 8px
overflow hidden
border var(--km-button-border)
background var(--hl-bg)

&:hover
text-decoration none !important
box-shadow var(--km-subt-shadow)
border var(--km-light-border)

.avatar
transition all .6s
border-radius 50%
background-size cover
width 100px
height 100px

.card-tag
position absolute
color white
font-size 10px
left 0
top 0
padding 4px 8px
background #0084FF
z-index 1
border-bottom-right-radius 12px
transition all .6s

.text
display flex
flex-direction column
padding-right 10px

.name
max-width 100%
font-weight bold
text-overflow ellipsis
white-space nowrap
overflow hidden

.descr
margin-bottom 10px
font-size small
overflow hidden
max-height 52px

&.beautify .flink-item
width calc(25% - 16px)
height 210px
margin 8px
transition all .6s

+maxWidth(1000)
width calc(33% - 16px)

+maxWidth900()
width calc(25% - 16px)

+maxWidth(700)
width calc(33% - 16px)

+maxWidth(500)
width calc(50% - 16px)

+maxWidth(350)
width calc(100% - 16px)

.card-bg
position relative
height 60%
min-width 100%
object-fit cover
margin 0 !important
transition all .6s !important
filter blur(0)

.info
position relative
height 40%
transition background .6s, height .6s

.avatar
position absolute
left -32px
top -32px
transform scale(.2)

.text
position relative
top 0
margin-left 30px
transition all .6s

+minWidth(500)
&:hover
.card-bg
height 35%
filter blur(3px)

.info
height 66%
background #F2B94B
color white

.avatar
left -10px
top -50px
transform rotateZ(-35deg) rotateX(360deg) rotateY(360deg) scale(.6)

.text
top 26px
margin-left 10px

.descr
max-height 78px

.card-tag
left -100%

&.terse
&:not(.deprecated)
.flink-item
height 100px

.flink-item
width calc(25% - 12px)
padding 6px
margin 6px

+maxWidth1024()
width calc(33% - 12px)

+maxWidth(1000)
width calc(50% - 12px)

+maxWidth900()
width calc(33% - 12px)

+maxWidth(700)
width calc(50% - 12px)

+maxWidth(450)
width calc(100% - 12px)

.info
position relative
height 100%
width 100%

.avatar
position absolute
transform scale(.7)
left -10px
top -6px

.text
position relative
left 85px
width calc(100% - 80px)
transition all .6s

+minWidth(600)
&:hover
.avatar
transform rotateX(180deg) rotateY(180deg) scale(.01)
left -100px
opacity 0

.text
left 10px
width calc(100% - 20px)

.text:first-child
left 10px
width calc(100% - 20px)

编写 YML

  然后我们还需要编写友链的 YML 文件,格式要求如下:

1
2
3
4
5
6
7
8
9
10
- class_name: name        # 分类名,任意字符串
beautify: true # 是否使用美化格式(缺省为 false)
deprecated: false # 是否标记为失联友链(缺省为 false)
link_list: # 友链列表
- name: name # 友链名称,任意字符串
link: url # 友链地址
descr: text # 简介,不要太长
siteshot: url # 美化格式的卡片上方的图片链接
# ......
# ......

生成精灵图

  接下来我们需要生成一个精灵图,有两种选择:

  1. 手动拼接
  2. 使用工具自动生成

  前者的话找一个图片编辑软件自己一个一个按顺序拼接起来就可以了,每个头像必须为正方形且大小相同、纵向排列。

  我写了一个自动化生成的工具,可以供小伙伴们使用:

  该软件使用 Kotlin 编写,运行环境的最低要求为 JRE8,使用时通过命令启动即可:

1
java -jar ./SpriteBuilder.jar [args]

  目前支持的参数有:

参数名值类型解释(缺省值)
ymlString友链文件的路径(./source/_data/link.yml
outputString输出文件的路径,不带拓展名(./sprites/avatar
sizeInt单个头像的大小(120
retryInt网络拉取重试次数(2
limitInt每张精灵图的头像数量限制,填 <= 0 的数表明无限制(0
formatString输出的图像格式,支持 jpg 和 png(jpg
localString本地图像库地址(./.sprite_data/
timeoutInt网络拉取超时时间,单位 ms(10000

  该工具支持解析友链 YML 的信息,然后拼接出图片,只支持导出 jpg 格式。

  YML 的格式要求如下:

1
2
3
4
5
6
7
8
9
10
11
- deprecated: false     # 为 true 时该分类不生成头像,缺省为 false
link_list:
- link: url # 友链地址
avatar: url0 # 头像的地址,以`file://`开头时会从本地读取(例如:file//D:/a.jpg)
- link: url # 友链地址
avatar: url1
# ......
- deprecated: false
link_list:
# ......
# ......

  工具支持解析ImageIO.read支持的所有图像格式,同时附加webp格式支持,基本覆盖常用格式,解析图像时使用图像的二进制数据判断图像格式。

  本地图像库用于自动替换友链的头像,比如有如下 yaml:

1
2
3
- link_list:
- link: https://kmar.top/
avatar: https://image.xxx.top/qq.jpg

  工具会在本地图像库中检查目录中是否存在名为kmar.*的文件,如果有的话会直接使用本地图像库中的图片,不会发起网络请求。

  在本地库中查找时,会使用link的域名(不带二级域名和顶级域名)作为文件名称,比如:

  • https://kmar.top/ - kmar
  • https://www.kmar.top/ - kmar
  • https://abc.github.io/ - github

  如果本地图像库中存在多个文件名一致但拓展名不一致的文件,会选择最后一个。

  工具在拉取文件时的请求头设置如下:

1
2
3
referer: 'https://sprite-builder.kmar.top/'
user-agent: 'sprite-builder'
accept: 'image/webp, image/*'

  当拉取文件遇到 403 错误时,会尝试将referer设置为空,然后重新进行拉取(如果没有超出重试次数限制的话)。


创作不易,扫描下方打赏二维码支持一下吧ヾ(≧▽≦*)o