闲逛Mo 的记事簿的时候,看到了页脚的宠物挂件,瞬间来了灵感,大叔刚刚重刷的 Season’s Album 封面素材,正好可以用上,所以话不多说,搞起。

梦爱吃鱼做法

简单分析一下梦爱吃鱼的做法:通过修改 “bbTimeList.pug”,在首页顶部插入挂件。页脚的做法就简单多了,直接把 “footer-animal.js” inject 进去。

如果是安知鱼主题,直接替换 themes\anzhiyu\layout\includes\bbTimeList.pug 文件。

bbTimeList.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
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
if site.data.essay
each i in site.data.essay
if i.home_essay
- let onclick_value = theme.pjax.enable ? `pjax.loadUrl("/essay/");` : '';
- let href_value = theme.pjax.enable ? 'javascript:void(0);' : `/essay/`;
#bbTimeList.bbTimeList.container
i.anzhiyufont.anzhiyu-icon-jike.bber-logo.fontbold(onclick=onclick_value, title="即刻短文", href=href_value, aria-hidden="true")
#bbtalk.swiper-container.swiper-no-swiping.essay_bar_swiper_container(tabindex="-1")
#bber-talk.swiper-wrapper(onclick=onclick_value)
each i in site.data.essay
each item, index in i.essay_list
if index < 10
- var contentText = item.image ? item.content + ' [图片]' : (item.video ? item.content + ' [视频]' : item.content)
a.li-style.swiper-slide(href=href_value)= contentText
a.bber-gotobb.anzhiyufont.anzhiyu-icon-circle-arrow-right(onclick=onclick_value, href=href_value, title="查看全文")
img.con-animals.entered.loaded(id="new-con-animals" src="")
script(src=url_for(theme.home_top.swiper.swiper_js))

style.
#bbTimeList {
position: relative;
}

.con-animals {
display: block;
position: absolute;
max-width: 260px;
top: -85px;
z-index: 2;
}

@media screen and (max-width: 1200px) {
.con-animals {
display: none;
}
}

script.
// 将lastImageUrl移到全局作用域
window.lastImageUrl = window.lastImageUrl || '';

function setRandomImage() {
const img = document.getElementById('new-con-animals');
const imageUrls = [
"https://i1.wp.com/ruom.wuaze.com/i/2024/10/18/901216.webp",
"https://i1.wp.com/ruom.wuaze.com/i/2024/10/18/074167.webp",
"https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/759434.webp",
"https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/526748.webp",
"https://i1.wp.com/ruom.wuaze.com/i/2024/10/18/429029.webp"
];

let randomImage;
do {
randomImage = imageUrls[Math.floor(Math.random() * imageUrls.length)];
} while (randomImage === window.lastImageUrl);

window.lastImageUrl = randomImage;

if (img) {
img.src = randomImage;
}
}

function initializeDragImage() {
const img = document.getElementById('new-con-animals');
const container = document.getElementById('bbTimeList');

if (!img || !container) return;

if (!window.lastImageUrl) {
setRandomImage();
} else {
img.src = window.lastImageUrl;
}

let isDragging = false, wasDragged = false, startX, startLeft;
const containerWidth = container.clientWidth;
const imgWidth = img.clientWidth;
const maxLeft = containerWidth - imgWidth;
const edgeThreshold = 20;
let lastLeft = parseInt(localStorage.getItem('imgPositionLeft')) || 0;
lastLeft = Math.min(maxLeft, Math.max(0, lastLeft));
img.style.left = `${lastLeft}px`;

const savePosition = (left) => localStorage.setItem('imgPositionLeft', left);

img.addEventListener('click', () => {
if (!wasDragged) {
let currentLeft = lastLeft;
let newLeft;

if (currentLeft <= edgeThreshold) {
newLeft = Math.min(currentLeft + 200, maxLeft);
} else if (currentLeft >= maxLeft - edgeThreshold) {
newLeft = Math.max(currentLeft - 200, 0);
} else {
newLeft = currentLeft + (Math.random() < 0.5 ? -200 : 200);
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
}

if (newLeft !== lastLeft) {
lastLeft = newLeft;
img.style.left = `${newLeft}px`;
savePosition(newLeft);
}
}
});

img.addEventListener('mousedown', (e) => {
isDragging = true;
wasDragged = false;
startX = e.clientX;
startLeft = lastLeft;
img.style.transition = 'none';

const onMouseMove = (e) => {
if (!isDragging) return;
wasDragged = true;
const offsetX = e.clientX - startX;
lastLeft = Math.max(0, Math.min(startLeft + offsetX, maxLeft));
requestAnimationFrame(() => {
img.style.left = `${lastLeft}px`;
});
};

const onMouseUp = () => {
isDragging = false;
img.style.transition = 'left 0.5s ease-in-out';
savePosition(lastLeft);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};

document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}

document.addEventListener('DOMContentLoaded', initializeDragImage);
document.addEventListener('pjax:success', initializeDragImage);

_config.anzhiyu.ymlinject 中指定脚本位置:<script src="/static/footer-animal.js" defer></script>。新建 footer-animal.js 文件:

footer-animal.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
function initFooterAnimal() {
const footerBar = document.querySelector('#footer-bar');
if (!footerBar)
return console.error('找不到指定元素');

const footerAnimal = document.createElement('div');
footerAnimal.id = 'footer-animal';
footerAnimal.innerHTML = `
<img class="animal entered loaded"
src="https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/473503.webp"
alt="动物" />
`;

footerBar.insertAdjacentElement('beforebegin', footerAnimal);

const style = document.createElement('style');
style.textContent = `
#footer-animal {
position: relative;
}
#footer-animal::before {
content: '';
position: absolute;
bottom: 0;
width: 100%;
height: 36px;
background: url(https://i1.wp.com/ruom.wuaze.com/i/2024/10/19/351933.webp) repeat center / auto 100%;
box-shadow: 0 4px 7px rgba(0,0,0,.15);
}
.animal {
position: relative;
max-width: min(974px, 100vw);
margin: 0 auto;
display: block;
}
#footer-bar {
margin-top: 0 !important;
}
@media screen and (max-width: 1023px) {
#footer-animal::before {
height: 4vw;
}
}
[data-theme=dark] #footer-animal {
filter: brightness(.8);
}
`;
document.head.appendChild(style);
}

document.addEventListener('DOMContentLoaded', initFooterAnimal);
document.addEventListener('pjax:success', initFooterAnimal);

修改思路

首先,要修改主题文件,嗯,不能做,不能做,后期升级维护,太麻烦,老规矩,直接 inject 解决一切问题。

无论是顶部还是页脚,插入动作一致,插入 function 可以复用。稍等,顶部还是随机出现的小动物?有点意思,这个随机,页脚也能用的吧。行,先琢磨一下调用时的参数:

  • containerId:注入的容器 ID
  • imgId: 图片 ID
  • imgUrl - 图片 URL
  • imgPos - 图片位置
  • initMode - 初始位置,left、right、random、stored

顶部是查询 ID bbTimeList,插入 <img>,底部是插入带 <div> 包裹的 <img>,不行,得改……先自己 initFooterBar() 一个 footerList,这样插入流程就一样了。

好,结构搭好了:

plugged-imgs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
function createDraggableImage(containerId, imgId, imgUrl, storageKey, initMode) {
...
}

function initAll() {
initFooterBar();
initHeaderImage();
initFooterImage();
}

document.addEventListener('DOMContentLoaded', initAll);
document.addEventListener('pjax:success', initAll);

其中,createDraggableImage 参考梦爱吃鱼页脚的做法就可以了。

顶部修改

发现一个小问题,全屏首页时,在最底部露出插入图片的一部分,看着难受,接着改吧…

本来打算直接监听滚动来隐藏图片,反复观察滚动首页时发现可以通过监听 <header class="full_page">class 属性变化,当 nav-fixed 被添加或移除时,动态更新图片的 hidden 类。这种方式比直接监听滚动更可靠,因为它不依赖滚动事件,而是直接观察 DOM 属性的变化。好,搞起:

plugged-imgs.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
function initHeaderImage() {
const HeaderImageUrl = HEADER_IMG[Math.floor(Math.random() * HEADER_IMG.length)];

createDraggableImage(
'bbTimeList',
'new-header-img',
HeaderImageUrl,
'headerImgPos',
'stored'
);

const headerImg = document.getElementById('new-header-img');
const headerElem = document.querySelector('header.full_page');
if (!headerImg || !headerElem) return;
const updateImageClass = () => {
if (headerElem.classList.contains('nav-fixed')) {
headerImg.classList.remove('hidden');
} else {
headerImg.classList.add('hidden');
}
};

updateImageClass();

if (headerObserver) headerObserver.disconnect();
headerObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') updateImageClass();
});
});

headerObserver.observe(headerElem, { attributes: true });
}

页脚修改

页脚部分就简单的多了,直接一个随机函数,用洗牌算法,每次随机 1 ~ 3 个图片,像这样:

plugged-imgs.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
function initFooterImage() {
const footerContainer = document.getElementById('footerList');
if (!footerContainer) return;

const existingImages = footerContainer.querySelectorAll('.draggable-img');
existingImages.forEach(img => img.remove());

const imgCount = Math.floor(Math.random() * 3) + 1;
const shuffled = [...FOOTER_IMG];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
const footerImgUrls = shuffled.slice(0, imgCount);

for (let i = 0; i < footerImgUrls.length; i++) {
createDraggableImage(
'footerList',
`new-footer-img-${i+1}`,
footerImgUrls[i],
`footerImgPos_${i+1}`,
'random'
);
}
}

css 修改

css 还是不集成到 js 里吧,独立管理,这样方便后续修改。都是些常规操作,只要注意下页脚图片的 z-index 顺序就可以了。

最终成果

1
2
3
4
5
inject:
head:
- <link rel="stylesheet" href="/css/plugged-imgs.css">
bottom:
- <script src="/js/plugged-imgs.js"></script>
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
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
(function() {
const HEADER_IMG = [
"/img/header-01.png",
"/img/header-02.png",
"/img/header-03.png",
"/img/header-04.png",
"/img/header-05.png",
"/img/header-06.png",
"/img/header-07.png",
"/img/header-08.png",
"/img/header-09.png",
"/img/header-10.png"
];

const FOOTER_IMG = [
"/img/footer-01.png",
"/img/footer-02.png",
"/img/footer-03.png",
"/img/footer-04.png",
"/img/footer-05.png",
"/img/footer-06.png",
"/img/footer-07.png",
"/img/footer-08.png",
"/img/footer-09.png",
"/img/footer-10.png"
];

let headerObserver = null;

function createDraggableImage(containerId, imgId, imgUrl, storageKey, initMode) {
const container = document.getElementById(containerId);
if (!container) return;

if (document.getElementById(imgId)) return;

const originalPosition = getComputedStyle(container).position;
if (originalPosition === 'static') {
container.style.position = 'relative';
}

const img = document.createElement('img');
img.id = imgId;
img.className = 'draggable-img entered loaded';
img.draggable = false;
img.src = imgUrl;
container.appendChild(img);

let isDragging = false;
let wasDragged = false;
let startX = 0;
let startLeft = 0;
let currentLeft = 0;

const edgeThreshold = 20;
const moveStep = 200;
const shouldStore = initMode === 'stored';

const getMaxLeft = () => {
const containerWidth = container.clientWidth;
const imgWidth = img.clientWidth;
return Math.max(0, containerWidth - imgWidth);
};

const setPosition = (left, store = shouldStore) => {
const maxLeft = getMaxLeft();
const clampedLeft = Math.min(maxLeft, Math.max(0, left));
img.style.left = `${clampedLeft}px`;
currentLeft = clampedLeft;
if (store) localStorage.setItem(storageKey, clampedLeft);
};

const initPosition = () => {
const maxLeft = getMaxLeft();
let initLeft;
if (initMode === 'random') {
initLeft = Math.random() * maxLeft;
} else if (initMode === 'left') {
initLeft = 0;
} else if (initMode === 'right') {
initLeft = maxLeft;
} else {
const storedLeft = parseInt(localStorage.getItem(storageKey));
if (!isNaN(storedLeft) && storedLeft >= 0 && storedLeft <= maxLeft) {
initLeft = storedLeft;
} else {
initLeft = maxLeft;
}
}
setPosition(initLeft, shouldStore);
};

if (img.complete) {
initPosition();
} else {
img.addEventListener('load', initPosition);
}

window.addEventListener('resize', () => {
if (currentLeft !== undefined) {
const maxLeft = getMaxLeft();
if (currentLeft > maxLeft) {
setPosition(maxLeft, shouldStore);
} else {
img.style.left = `${currentLeft}px`;
}
}
});

img.addEventListener('click', (e) => {
if (!wasDragged) {
const maxLeft = getMaxLeft();
let newLeft = currentLeft;
if (currentLeft <= edgeThreshold) {
newLeft = Math.min(currentLeft + moveStep, maxLeft);
} else if (currentLeft >= maxLeft - edgeThreshold) {
newLeft = Math.max(currentLeft - moveStep, 0);
} else {
newLeft = currentLeft + (Math.random() < 0.5 ? -moveStep : moveStep);
newLeft = Math.max(0, Math.min(newLeft, maxLeft));
}
if (newLeft !== currentLeft) setPosition(newLeft, shouldStore);
}
wasDragged = false;
});

img.addEventListener('mousedown', (e) => {
e.preventDefault();
isDragging = true;
wasDragged = false;
startX = e.clientX;
startLeft = currentLeft;
img.style.transition = 'none';

const onMouseMove = (e) => {
if (!isDragging) return;
wasDragged = true;
const offsetX = e.clientX - startX;
const maxLeft = getMaxLeft();
let newLeft = startLeft + offsetX;
newLeft = Math.min(maxLeft, Math.max(0, newLeft));
requestAnimationFrame(() => {
img.style.left = `${newLeft}px`;
currentLeft = newLeft;
});
};

const onMouseUp = () => {
if (!isDragging) return;
isDragging = false;
img.style.transition = '';
if (shouldStore) localStorage.setItem(storageKey, currentLeft);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
};

document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}

function initFooterBar() {
if (document.getElementById('footerList')) return;
const footerBar = document.getElementById('footer-bar');
if (!footerBar) return;
const footerList = document.createElement('div');
footerList.id = 'footerList';
footerList.classList.add('footerList', 'container');
footerBar.insertAdjacentElement('beforebegin', footerList);
}

function initHeaderImage() {
const HeaderImageUrl = HEADER_IMG[Math.floor(Math.random() * HEADER_IMG.length)];

createDraggableImage(
'bbTimeList',
'new-header-img',
HeaderImageUrl,
'headerImgPos',
'random'
);

const headerImg = document.getElementById('new-header-img');
const headerElem = document.querySelector('header.full_page');
if (!headerImg || !headerElem) return;
const updateImageClass = () => {
if (headerElem.classList.contains('nav-fixed')) {
headerImg.classList.remove('hidden');
} else {
headerImg.classList.add('hidden');
}
};

updateImageClass();

if (headerObserver) headerObserver.disconnect();
headerObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') updateImageClass();
});
});

headerObserver.observe(headerElem, { attributes: true });
}

function initFooterImage() {
const footerContainer = document.getElementById('footerList');
if (!footerContainer) return;

const existingImages = footerContainer.querySelectorAll('.draggable-img');
existingImages.forEach(img => img.remove());

const imgCount = Math.floor(Math.random() * 3) + 1;
const shuffled = [...FOOTER_IMG];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
const footerImgUrls = shuffled.slice(0, imgCount);

for (let i = 0; i < footerImgUrls.length; i++) {
createDraggableImage(
'footerList',
`new-footer-img-${i+1}`,
footerImgUrls[i],
`footerImgPos_${i+1}`,
'random'
);
}
}

function initAll() {
initFooterBar();
initHeaderImage();
initFooterImage();
}

document.addEventListener('DOMContentLoaded', initAll);
document.addEventListener('pjax:success', initAll);
})();
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
#footerList {
position: relative;
height: 260px;
}

#footerList::before {
content: '';
position: absolute;
bottom: 0px;
height: 110px;
width: 100%;
background: url("/img/footerbg.webp") repeat center / auto 100%;
z-index: 3;
}

#footer-bar {
margin-top: 0;
box-shadow: 0 -3px 7px rgba(0,0,0,.15);
}

[data-theme="dark"] #footer {
background: none;
}

[data-theme="dark"] #footerList::before {
filter: brightness(.5);
}

[data-theme="dark"] #footer-bar {
box-shadow: 0 -3px 7px rgba(255,255,255,.12);
}

#new-header-img, #new-footer-img-1,
#new-footer-img-2, #new-footer-img-3 {
display: block;
position: absolute;
margin: 0 auto;
cursor: grab;
pointer-events: auto;
-webkit-user-select: none;
user-select: none;
transition: left 0.5s ease-in-out;
z-index: 2;
}

#new-header-img.hidden {
display: none;
}

#new-header-img:active, #new-footer-img-1:active,
#new-footer-img-2:active, #new-footer-img-3:active {
cursor: grabbing;
}

#new-header-img {
top: -85px;
}

#new-footer-img-1, #new-footer-img-2, #new-footer-img-3 {
bottom: 0;
}

[data-theme="dark"] #new-footer-img-1,
[data-theme="dark"] #new-footer-img-2,
[data-theme="dark"] #new-footer-img-3 {
filter: brightness(.5);
}

@media screen and (max-width:1200px){
#new-footer-img-3 {
display: none;
}
}

@media screen and (max-width:768px){
#new-header-img,
#new-footer-img-2 {
display: none;
}
}

LeoC © 2026 | 萌 ICP 备 20260209 号

本站已运行...数学不好,还在数呢...,发表 6 篇文章,总计 5.5k 字,有 位访客,访问