前言

温馨提示:本日记本只提供魔改参考,并不做通用处理,很多源码修改都需要根据自己博客做小小微调

另外在修改源码时注意做好备份处理。本帖只提供思路与方法,如果哪里炸了,请检查语法与缩进等,猹概不负责啦!

继白嫖店长冰老师小康小N等大佬的魔改之后,猹终于不满于白嫖,开始想动手实操,把自己的博客调教成想要的样子,也以此来使猹学前端有实操的机会。

:这部分前言内容会在魔改日记本的每一篇都出现;另外这是一篇日记而非教程,文风可能不是那么友好(逃

本篇特别提示:这次的魔改修改了bf的主要结构,并对一些大的布局样式进行了flex弹性盒子的全面替换,不建议进行跟随魔改(我自己都不记得我改的一些细节)


魔改起因

事情要从小菜单的魔改说起。猹将文章页的目录模块藏在了小菜单里头,结果经过大量的反馈发现,“猹你博客没有目录吗”

az,好吧本来是为了保留作者卡片而设计的小菜单,既然目录没人看到,那小菜单显然也是没人点开看。本来的目的也就没有达到,想想还是把小菜单砍了,重构吧……

主要结构重构-左右双栏与菜单按钮

设计来源、灵感

重构的最主要目的还是原本容易被人忽略的目录模块。找到一个好的目录布局设计可不容易,bf原先的设计也的确不适合我。在zhheo的推荐下,选用了少数派的目录设计。


少数派的设计逻辑说实在很戳我的心,然后我脑子一抽决定给他加个当前标题居中显示。。。再然后折腾了几天就是现在这样了。

设计思路

但是折腾这个目录的关键在于,如何保证其自适应良好。采取与小菜单相同的浮动窗口肯定是不好用的,一不小心可能就会变成这个样子(拿同学在做的博客系统举个栗子(反正目录是我的设计))

如果要保证文章板块与目录间天然的自适应,最好的方式还是在左右侧各开辟一个 div 盒子,这样更可靠一点。原本 butterfly 主题的源码部分,是这样的:

<div id="mainbody">
<main class="layout hide-aside" id="content-inner">
<div id="post">...</div>
</main>
</div>

并且对main部分限定了最大宽度1000px,在双栏布局下双栏也被严格限制在这个大小里,属实是对魔改的发挥起到了一定的限制。故而更改其结构是十分必要的事。于是目标结构如下:

  <div id="mainbody">
+ <div id="content_leftside">...</div>
<main class="layout hide-aside" id="content-inner">
<div id="post">...</div>
</main>
+ <div id="content_rightside">...</div>
</div>

此后,显然就可以直接在新开辟的content_rightside加点料。如果只是建立起类似样式的目录倒是小事,实现当前标题的恒定居中才是复杂的东西。

最后是一些功能按键。回到顶部这个功能显然还是放在类似小彩单的位置合适,样式直接参考fluit主题的按钮即可。而原本的作者卡片我决定提取社交按钮以及一些功能按键,在左侧也形成一个居中对称的设计来进行视觉上的平衡,魔改思路就此完成。

布局调整-开辟左右双栏

将开辟双栏的思路翻译到 pug 源码文件[BlogSource]/themes/butterfly/layout/includes/layout.pug

        #mainbody
+ block content_leftside
main#content-inner.layout(class=hideAside)
if body
div!= body
else
block content
if theme.aside.enable && page.aside !== false
include widget/index.pug
+ block content_rightside

有了左右双栏,目录等的添加就好做了。

目录1-原生目录js再就业

content_rightside pug源码
block content_rightside
- let tocNumber = page.toc_number !== undefined ? page.toc_number : theme.toc.number
#content_rightside
#rightside-toc
.toc-box
if (page.encrypt == true)
.toc-content.toc-div-class(style="display:none")!=toc(page.origin, {list_number: tocNumber})
else
.toc-content!=toc(page.content, {list_number: tocNumber})
#rightside-button
button#rightside-go-up(type="button" title=_p("rightside.back_to_top"))
i.fas.fa-arrow-up

为了方便自适应,按键也一并写在content_rightside

目录板块在小菜单的魔改中就已经写过了,问题在于active状态添加的js失效了。咱本来就不太懂js,自然不愿去再写一份js来对当前标题进行标记。最有可能的方式,自然是对原本的js进行改造再就业。

    const tocFn = function () {
- const $cardTocLayout = document.getElementById('card-toc')
+ const $cardTocLayout = document.getElementById('rightside-toc')
const $cardToc = $cardTocLayout.getElementsByClassName('toc-content')[0]
const $tocLink = $cardToc.querySelectorAll('.toc-link')
const $article = document.getElementById('article-container')

但修改完对应类等发现,还是没办法令其生效。于是将目光转向了一些配置项中,最终发现是侧栏的关闭导致了js无法生效。修改文件[BlogSource]/themes/butterfly/layout/includes/head/config_site.pug

-   var showToc = is_post() && theme.aside.enable && pageToc && (toc(page.content) !== '' || page.encrypt == true )
+ var showToc = is_post() && pageToc && (toc(page.content) !== '' || page.encrypt == true )

active能够正常标记到目录标题中,就可以编写对应的样式。少数派中的胶囊颗粒采用的是在标题前使用div,所有标题也使用div盒子。胶囊与盒子本身不是绝对关联的情况下才有可能做到所有不处于active状态的标题隐藏而胶囊不隐藏的效果。

butterfly的设计下目录则采用有序表格的方式呈现。这也意味着在不改目录生成源码的情况下,我如果想单纯实现胶囊就只能使用before伪类,也没有办法做到一模一样的显隐逻辑,于是只能自己设计效果了。

这里不得不吐槽下不同长度胶囊颗粒的css实现(这写的好淦啊)

.toc-item
transition: all 0.5s ease-in-out
opacity: 0.5
// 目录缩进结构
&.toc-level-1
&:before
content: " "
margin: 0.65rem 0.4rem 0.65rem 0
width: 1rem
height: 0.2rem
border-radius: 0.1rem
background: #000
&.toc-level-2
&:before
content: " "
margin: 0.65rem 0.7rem 0.65rem 0
width: 1rem
height: 0.2rem
border-radius: 0.1rem
background: #000
&.toc-level-3
&:before
content: " "
margin: 0.65rem 1.2rem 0.65rem 0
width: 0.8rem
height: 0.2rem
border-radius: 0.1rem
background: #000
&.toc-level-4
&.toc-level-5
&.toc-level-6
&:before
content: " "
margin: 0.65rem 1.7rem 0.65rem 0
width: 0.6rem
height: 0.2rem
border-radius: 0.1rem
background: #000

目录2-当前标题居中轮转

居中的轮盘设计就类似于一些卡牌决斗盘抽卡,一些动效就很养眼。本来想使用swiper来实现,但我不知道怎么去控制其随着文章进度转换,于是作罢。

最终选用了十分暴力的 translate 大法,复用目录的标记js部分进行相应的 translate 计算即可动态偏移:

var offset = -currentIndex * 1.5 + 4.25
$cardToc.style.translate = "0 " + offset + "rem"

然后css对对应的标题高度进行限制(例如与上面对应的就是一行1.5rem),保证偏移后还是居中就行。

translate 大法也就有个反人类设计吧,没办法使用滚轮查看目录。

子菜单-回到顶部

回到顶部的按钮还是要做一点点调整的,现在的整体设计是向中间集中的,不适合在边角放小彩单。而放到偏靠中间的部分还是需要考虑位置自适应问题。

fluit主题的回到顶部按钮位置本身是靠js进行控制的。而我这里做了一点点的尝试,直接在content_rightside进行样式测试。

意外的是,fixed布局似乎不会被flex干扰,默认会靠在边上而不会串成串儿。于是直接采用绝对定位将其钉在了content_rightside的左下角。

相关的js逻辑直接复用小彩单,在此不提。

左侧功能区-文章阅读进度与糖果屋二维码

content_leftside pug源码
block content_leftside
#content_leftside
#left_menu
//- 联系方式
if(theme.social)
.leftside-social
!=fragment_cache('social', function(){return partial('includes/header/social')})
//- 阅读进度
.leftside-contents
span#read-percent 0
//- 跳转评论
.leftside-comment
a(href="#post-comment" title="要去留言吗?")
i.fa-lg.fas.fa-comment
.leftside-qq(title="来糖果屋聊天噻!")
i.fa-lg.fas.fa-qrcode
.leftside-rmb
a(title="柿还在施工的捐赠页!不给看!")
i.fa-lg.fas.fa-money-check-dollar
#left_display
.candy_qrcode
img(src="./img/candy-qr.png")

在原先的bf的目录中,有一串数字表示阅读进度(它采用的不是全网页百分比,在我看来逻辑上更合理一点),恰好可以进行复用。在左侧的功能区一群图标中,插入一个数字恰好可以看起来对称些。

然后是一些功能,分享按钮与跳转评论本身只是链接,赞助二维码还没画好(涩涩嘿嘿涩涩)

所以只有一个糖果屋群聊二维码需要加上。我去使用工具绘制了一下一个相对好看且对称的二维码:

然后以类似的方法添加上去。但是这玩意肯定不能常态显示,不然我倒数第二个图标就是摆设了啊。想想我决定动手写一个js。

本身我是没有js编写经验的(以前反正或抄或改,反正就是没有自己写过),好在写了类似hover的逻辑还算简单:

document.addEventListener('DOMContentLoaded', function () {
const leftSideMenu = {
qrcodeHover: () => {
// 按钮获取
const left_menu = document.getElementById('left_menu')
if(left_menu){
const leftside_qq = left_menu.getElementsByClassName('leftside-qq')[0]
// 显示区域获取
const left_display = document.getElementById('left_display')
const candy_qrcode = left_display.getElementsByClassName('candy_qrcode')[0]

leftside_qq.addEventListener('mouseover', function () {
candy_qrcode.style.opacity = "1"
})
leftside_qq.addEventListener('mouseout', function () {
candy_qrcode.style.opacity = "0"
})
}
}
}

leftSideMenu.qrcodeHover();
})

整体显隐逻辑

左右栏还是需要相应的显示逻辑的。文章处于最顶部时不应该显示左右栏和回到最顶按钮,阅读结束后不应显示左右栏。于是这里选择复用原小菜单显隐逻辑代码,做进一步修改:

window.scrollCollect = () => {
return btf.throttle(function (e) {
const currentTop = window.scrollY || document.documentElement.scrollTop
const isDown = scrollDirection(currentTop)

//滚动条高度+视窗高度 = 可见区域底部高度
const currentBottom = currentTop + document.documentElement.clientHeight;

if (currentTop > 56) {
if (isDown) {
if ($header.classList.contains('nav-visible')) $header.classList.remove('nav-visible')
if (isChatBtnShow && isChatShow === true) {
chatBtnHide()
isChatShow = false
}
} else {
if (!$header.classList.contains('nav-visible')) $header.classList.add('nav-visible')
if (isChatBtnHide && isChatShow === false) {
chatBtnShow()
isChatShow = true
}
}
$header.classList.add('nav-fixed')
// 获取位置监测容器,此处采用文章主体
var eventlistner = document.getElementById('post');
if(eventlistner){
var centerY = eventlistner.offsetTop+eventlistner.offsetHeight;
if(centerY > currentBottom && currentTop > eventlistner.offsetTop+document.getElementById('post-info').offsetHeight){
if($rightside_toc) $rightside_toc.style.cssText = 'opacity: 1; pointer-events: all'
if($leftside_menu) $leftside_menu.style.cssText = 'opacity: 1; pointer-events: all'
} else {
if($rightside_toc) $rightside_toc.style.cssText = 'opacity: 0; pointer-events: none'
if($leftside_menu) $leftside_menu.style.cssText = 'opacity: 0; pointer-events: none'
}
}
if($rightside_button) $rightside_button.style.cssText = 'bottom: 1rem'
} else {
if (currentTop === 0) {
$header.classList.remove('nav-fixed', 'nav-visible')
}
if($rightside_toc) $rightside_toc.style.cssText = 'opacity: 0; pointer-events: none'
if($leftside_menu) $leftside_menu.style.cssText = 'opacity: 0; pointer-events: none'
if($rightside_button) $rightside_button.style.cssText = 'bottom: -3rem'
}

if (document.body.scrollHeight <= innerHeight) {
if($rightside_toc) $rightside_toc.style.cssText = 'opacity: 0; pointer-events: none'
if($leftside_menu) $leftside_menu.style.cssText = 'opacity: 0; pointer-events: none'
if($rightside_button) $rightside_button.style.cssText = 'bottom: -3rem'
}
}, 200)()
}

显示范围这里选择直接检测文章主体。

css完整代码和gh提交记录

样式代码
#mainbody
display: flex
#post
background: none
box-shadow: none
&:hover
box-shadow: none
#content_leftside
flex: 1
width: fill-available
width: -webkit-fill-available
display: flex
flex-direction: row-reverse
#left_menu
top: -1rem
opacity: 0
pointer-events: none
transition: all 0.3s ease-in-out
position: fixed
display: flex
height: 100%
width: 1.5rem
padding: 0.5rem
margin: 0 auto
flex-direction: column
justify-content: center
align-items: center
translate: -0.7rem 0
i
color: #000
.leftside-social
text-align: center
opacity: 0.7
width: 1.5rem
margin: 0.4rem
transition: all 0.5s
.fa-fw
margin: 0.5rem 0
transition: all 0.3s
transform: scale(1.3)
&:hover
opacity: 1
.leftside-contents
.leftside-comment
.leftside-qq
.leftside-rmb
text-align: center
margin-bottom: 0.25rem
opacity: 0.7
width: 1.5rem
height: 1.5rem
font-weight: bold
transition: all 0.5s
a
font-weight: bolder
.fas
margin: 0.5rem 0
width: 1.25em
.fas::hover
transform: scale(1.3)
&:hover
opacity: 1
.leftside-contents
border-radius: 1rem
box-shadow: 0 0 5px #000
margin-bottom: 0.5rem
#left_display
// box-shadow: 0 0 5px #000
width: inherit
height: 100%
margin-right: 3rem
display: flex
align-items: center
position: fixed
top: -0.62rem
flex-direction: row-reverse
.candy_qrcode
transition: all 0.3s ease-in-out
opacity: 0
pointer-events: none
max-width: 9rem
width: 100%


#content_rightside
flex: 1
width: fill-available
width: -webkit-fill-available
display: flex
flex-direction: row
#rightside-toc
top: -1rem
opacity: 0
pointer-events: none
transition: all 0.3s ease-in-out
position: fixed
display: flex
height: 100%
width: inherit
padding: 0.5rem
flex-direction: row
align-items: center

.item-headline
font-size: 1rem
font-weight: bold
display: flex
flex-direction: column
span
margin-left: 0.5rem
span.toc-text
color: #000
.toc-box
height: 10rem
width: 100%
// box-shadow: 0 0 5px #000
overflow: hidden
.toc-content
translate: 0 4.25rem
transition: all 0.5s
// height: 100%
ol
padding: 0
margin: 0
list-style-type: none
li::marker
content: none
li::before
float: left
// counter-increment: section
// content: counters(section,".") " "
.toc
font-weight: bold
width: 100%
height: inherit
overflow-y: scroll
overflow-x: hidden
// height: 10rem
margin-top: 0.1rem
// padding-left: 0
list-style-type: none
.toc-child
// padding: 0
list-style-type: none
.toc::-webkit-scrollbar
width: 0 !important
.toc-link
line-height: 1.5rem
max-height: 1.5rem
white-space: nowrap
display: block
// border-left: 0.1rem solid transparent
color: var(--btn-bg)
transition: all 0.5s
padding-left: 0.5rem
span.toc-text
text-overflow: ellipsis
color: #000
&:hover span.toc-text
color: #000
&.active
transform: scale(1.3)
transform-origin: 2.5rem 0.75rem
font-weight: bolder
.toc-item
transition: all 0.5s ease-in-out
opacity: 0.5
// 目录缩进结构
&.toc-level-1
&:before
content: " "
margin: 0.65rem 0.4rem 0.65rem 0
width: 1rem
height: 0.2rem
border-radius: 0.1rem
background: #000
&.toc-level-2
&:before
content: " "
margin: 0.65rem 0.7rem 0.65rem 0
width: 1rem
height: 0.2rem
border-radius: 0.1rem
background: #000
&.toc-level-3
&:before
content: " "
margin: 0.65rem 1.2rem 0.65rem 0
width: 0.8rem
height: 0.2rem
border-radius: 0.1rem
background: #000
&.toc-level-4
&.toc-level-5
&.toc-level-6
&:before
content: " "
margin: 0.65rem 1.7rem 0.65rem 0
width: 0.6rem
height: 0.2rem
border-radius: 0.1rem
background: #000


&.active
transition: all 0.5s ease-in-out
opacity: 1
.toc-child
display: block
&:hover
opacity: 1
#rightside-button
position: fixed
z-index: 101
display: flex
bottom: -3rem
align-items: center
justify-content: center
flex-direction: column
transition: all .5s

button
background: var(--card-bg)
height: 2rem
width: 2rem
border-radius: 1rem
font-size: 0.8rem
box-shadow: 0 0 5px #000
margin: 0.25rem 0

[data-theme="dark"]
#mainbody
#content_leftside
a
&:hover
color: #a153ff
#left_menu
.leftside-contents
box-shadow: 0 0 5px #fff
i
color: #fff
.leftside-contents span
color: #fff
#left_display
.candy_qrcode
filter: invert(1)
#content_rightside
#rightside-toc
span.toc-text
color: #fff
.toc-link
color: var(--btn-bg)
span.toc-text
color: #fff
&:hover span.toc-text
color: #fff
.toc-item
// 目录缩进结构
&.toc-level-1
&:before
background: #fff
&.toc-level-2
&:before
background: #fff
&.toc-level-3
&:before
background: #fff
&.toc-level-4
&.toc-level-5
&.toc-level-6
&:before
background: #fff

#rightside-button
button
background: var(--card-bg)
box-shadow: 0 0 5px #fff
i
color: #fff

.start
.end
font-family: 'Noto Serif SC', -apple-system, BlinkMacSystemFont, Roboto, Segoe UI, Helvetica, Arial, serif
font-size: large
font-weight: bolder
opacity: 0.5

@media screen and (max-width: 1200px)
#content_leftside
#left_menu
display: none !important
#content_rightside
#rightside-toc
display: none !important

@media screen and (max-width: 1100px)
#mainbody
display: block
#content_leftside
display: none !important
#content_rightside
display: none !important
github提交记录

微调-抛弃卡片结构和若干功能区

这部分包含了我很长时间的微调和想法,主要只针对整个文章块。

一是开头的标题部分:

我添加了彩色标签,并把一些数据做了重新排版。(西瓜做分割线真好用)

二是地下文章结束的部分:

我去除了版权卡片(以后大概还会想个设计补上)、上下篇和推荐(我觉得这玩意属实没什么用处,尽管我做了美化但也觉得很违和)

三是去除了文章板块的框框,整个与背景项贴合。整体看起来相对简单整洁一点。

结语

整个文章页魔改应该会告一段落。还剩下的一点坑是移动端的目录和回到顶部,这部分想还是用最传统的浮动小菜单来实现,就不瞎整了。

这部分的魔改太难以像前面的捋思路方式给源码修改参考,涉及的调整部分过多也不利于做教程。所以有能力的还是建议自己翻我博客源码来做更改,没能力的就爱莫能助了。