構建一個簡約博皮的過程
前置
由於之前構建的皮膚 reacg 偏二次元風,儘管提供了大量配置(包括幾乎任何顏色、插件等的配置),依然有人吐槽花里胡哨,遂重新構建了一款簡約風格的部落格園皮膚, 正如你所見。下文我將從零介紹它的構建過程,構建它最快花費一個小時到幾個小時。由於之前做了大量工作,所以現在按照流程走一遍就完事了。
準備工作
- 環境:node & npm
- git clone //gitee.com/guangzan/awescnb.git
- 運行
npm install
安裝依賴 - 在 themes 文件夾下新建 simple 文件夾、simple/index.js
配置
config/options.js,這是 webpack 的配置:
module.exports = {
themeName: 'simple',
template: 'index',
eslint: true,
sourceMap: false,
openAnalyzer: true,
cssExtract: false,
openBrowser: false,
// ...
}
看個人所需,我在這裡簡單配置如下:
- themeName: ‘simple’ 對應 themes/simple 文件夾
- template: 運行 npm start 打開部落格園首頁模板
- 開啟 eslint
- 開啟 Analyzer,運行 npm run build 時我需要檢查最終構建的體積大小
- cssExtract 先不分離 css,構建完成直接推送到遠端,直接切換線上版本
開始
- simple/style 下存放樣式文件
- index.scss
- index.m.scss
- markdown.scss
- reset.scss
- tools.scss
最終只在入口文件 index.js 中導入 index.scss,其他 scss 由 index.scss 引入,由於之前編好了樣式程式碼,所以需要新編寫的樣式極少。
- simple/plugins/index.js
引入構建好的部落格園插件,不需要寫任何功能程式碼,及其樣式。
import footer from '@plugins/footer'
import highlight from '@plugins/highlight'
import copy from '@plugins/copy'
import linenumbers from '@plugins/linenumbers'
import imagebox from '@plugins/imagebox'
import commentsAvatars from '@plugins/commentsAvatars'
import dragMenu from '@plugins/dragMenu'
import donation from '@plugins/donation'
import emoji from '@plugins/emoji'
import player from '@plugins/player'
import postMessage from '@plugins/postMessage'
import postSignature from '@plugins/postSignature'
import postTopimage from '@plugins/postTopimage'
import notice from '@plugins/notice'
const plugins = () => {
footer()
highlight()
copy()
linenumbers()
imagebox()
commentsAvatars()
donation()
dragMenu()
emoji()
player()
postMessage()
postSignature()
postTopimage()
notice()
}
module.exports = plugins
- 構建 footer 鏈接
- mac 樣式程式碼高亮
- 程式碼行號
- 圖片燈箱
- 顯示評論區頭像
- 捐增二維碼
- 按鈕工具條(返回頂部,推薦,收藏。。。)
- 評論框表情
- 播放器
- 在文章頭部構建文章資訊
- 構建文章簽名資訊
- 文章頭圖
- 通知功能
由於這個皮膚的基調是簡約,所以只引入一些常用的功能模組。花里胡哨的就不考慮了。
- simple/build/index.js
index.js 引入了一些其他 JavaScript 用來做一些調整,例如 simple/build 文件夾下我還寫了
- catalog(文章目錄)
- header(頭部導航邏輯)
- scroll(滾動控制)
- side (側邊欄邏輯)
catalog/index.js
程式碼有些長,我將它摺疊
import './index.scss'
import {
pageName,
userAgent,
hasPostTitle,
getClientRect,
throttle,
} from '@tools'
const { enable } = window.opts.catalog
// 構建目錄
function build() {
let $catalogContainer = $(
`<div id="catalog">
<div class='catalog-title'><h3>目錄</h3></div>
</div>`,
)
const $ulContainer = $('<ul></ul>')
const titleRegExp = /^h[1-3]$/
$('#cnblogs_post_body')
.children()
.each(function() {
if (titleRegExp.test(this.tagName.toLowerCase())) {
if ($(this).text().length === 0) return // 如果標題為空 只有 #
let id
let text
if (this.id !== '') {
id = this.id
text =
this.childNodes.length === 2
? this.childNodes[1].nodeValue
: this.childNodes[0].nodeValue
} else {
if (this.childNodes.length === 2) {
const value = this.childNodes[1].nodeValue
text = value ? value : $(this.childNodes[1]).text()
} else {
const value = this.childNodes[0].nodeValue
text = value ? value : $(this.childNodes[0]).text() // 處理標題被 span 包裹的情況
}
id = text.trim()
$(this).attr('id', id)
}
const title = `
<li class='${this.nodeName.toLowerCase()}-list'>
<a href='#${id}'>${text}</a>
</li>
`
$ulContainer.append(title)
}
})
const $catalog = $($catalogContainer.append($ulContainer))
$('#sidebar_news').after($catalog)
}
function noCatalog() {
if (pageName() !== 'post') return
// to do something
}
// 設置目錄活躍標題樣式
function setActiveCatalogTitle() {
$(window).scroll(
throttle(
function() {
for (let i = $('#catalog ul li').length - 1; i >= 0; i--) {
const titleId = $($('#catalog ul li')[i])
.find('a')
.attr('href')
.replace(/[#]/g, '')
const postTitle = document.querySelector(
`#cnblogs_post_body [id='${titleId}']`,
)
if (getClientRect(postTitle).top <= 10) {
if (
$($('#catalog ul li')[i]).hasClass('catalog-active')
)
return
$($('#catalog ul li')[i]).addClass('catalog-active')
$($('#catalog ul li')[i])
.siblings()
.removeClass('catalog-active')
return
}
}
},
50,
1000 / 60,
),
)
}
function setCatalogToggle() {
$(window).scroll(
throttle(
function() {
if ($('#catalog ul').css('display') === 'none') return
const bottom = getClientRect(
document.querySelector('#sideBarMain'),
).bottom
if (bottom <= 0) {
$('#catalog').addClass('catalog-sticky')
} else {
$('#catalog').removeClass('catalog-sticky')
}
},
50,
1000 / 60,
),
)
}
function toggle() {
$('.catalog-title').click(function() {
$('#catalog ul').toggle('fast', 'linear', function() {
$(this).css('display') === 'none'
? $('.catalog-title').removeClass('is-active')
: $('.catalog-title').addClass('is-active')
})
})
}
function catalog() {
if (
enable &&
hasPostTitle() &&
pageName() === 'post' &&
userAgent() === 'pc'
) {
build()
setActiveCatalogTitle()
setCatalogToggle()
toggle()
} else {
noCatalog()
}
}
module.exports = catalog
header/index.js
import './index.scss'
import { pageName, userAgent } from '@tools'
// header右側按鈕容器
const buildHeader = () => {
const gitee = window.opts.gitee
$('#navList').after(`<div class="navbar-end"></div>`)
$('#blog_nav_newpost').appendTo('.navbar-end')
$(
`<a href="//guangzan.gitee.io/awescnb-docs/" id="header-awescnb">構建新皮膚</a>`,
).appendTo('.navbar-end')
$(`<a href="${gitee.url}" id="header-gitee">開源主頁</a>`).appendTo(
'.navbar-end',
)
}
// 構建header昵稱
const headerNickname = () => {
$('#Header1_HeaderTitle').text($('#profile_block a:first').text())
}
// header頭像
const buildAva = () => {
const { avatar } = window.opts.theme
$('#blogLogo').attr('src', `${avatar}`)
}
// 隨筆頁構建文章題目
const headerInnerPostTitle = () => {
if (pageName() !== 'post') return
if (userAgent() !== 'pc') return
let title = $('.post .postTitle')
.text()
.replace(/\s*/g, '')
const titleLength = title.length
let offset = ''
if (0 <= titleLength && titleLength < 10) offset = '-180%'
if (10 <= titleLength && titleLength < 15) offset = '-140%'
if (15 <= titleLength && titleLength < 20) offset = '-100%'
if (20 <= titleLength && titleLength < 25) offset = '-65%'
if (25 <= titleLength && titleLength < 28) offset = '-60%'
if (titleLength >= 28) {
title = title.substring(0, 28) + '...'
offset = '-60%'
}
$('#navList').append(`<span class='header-posttitle'>${title}</span>`)
$('head').append(
`<style>
.header-posttitle {transform: translate3d(${offset}, 300%, 0);}
#header.is-active .header-posttitle {transform: translate3d(${offset}, 0, 0);}
</style>`,
)
}
// header移動端菜單
const headerBtn = () => {
const ele = `<div id="navbarBurger" class="navbar-burger burger" data-target="navMenuMore">
<span></span>
<span></span>
<span></span>
</div>`
$('#blogTitle').append(ele)
$('#navbarBurger').click(function() {
$(this).toggleClass('is-active')
$('#navigator').toggleClass('is-active')
})
}
// 創建自定義圖標容器及其圖標
const customLinks = () => {
const github = window.opts.github
// wrap
$('.navbar-end').prepend(`<div class="custom-links"></div>`)
$('#blogTitle h2').after(`<div class="custom-links"></div>`)
// github icon
if (github.enable) {
$('.custom-links').append(`<a class="github" href="${github.url}"></a>`)
}
// qq
// $('.custom-links').append(`<a class="qq"></a>`)
// 知乎
$('.custom-links').append(`<a class="zhihu"></a>`)
}
// 首頁 header 不要上下翻滾
const preventHeaderChange = () => {
if (pageName() !== 'index') return
$('#header').addClass('navlist-fix')
}
const header = () => {
headerNickname()
buildHeader()
buildAva()
headerBtn()
customLinks()
headerInnerPostTitle()
preventHeaderChange()
}
module.exports = header
scroll/index.js
// import './index.scss'
import { userAgent } from '@tools'
// 只觸發一次向上或向下
// 如果又重新反向滾動則再觸發一次
function scrollOnce() {
function scrollFunc() {
let scrollDirection
if (!scrollAction) {
scrollAction = window.pageYOffset
}
let diff = scrollAction - window.pageYOffset
if (diff < 0) {
scrollDirection = 'down'
} else if (diff > 0) {
scrollDirection = 'up'
} else {
// First scroll event
}
scrollAction = window.pageYOffset
return scrollDirection
}
let scrollAction, originalDir
$(window).scroll(function() {
if (userAgent() !== 'pc') return
let direction = scrollFunc()
if (direction && originalDir != direction) {
if (direction == 'down') {
$('#header').addClass('is-active')
$('#catalog').addClass('catalog-scroll-up')
$('#catalog').removeClass('catalog-scroll-down')
} else {
$('#header').removeClass('is-active')
$('#catalog').removeClass('catalog-scroll-up')
$('#catalog').addClass('catalog-scroll-down')
}
originalDir = direction
}
})
}
function scroll() {
scrollOnce()
// ...
}
module.exports = scroll
side/index.js
import './index.scss'
import { poll } from '@tools'
import { actions } from '@constants/element'
const sideItemToggle = () => {
for (const { title, content } of actions) {
if (!title.length) continue
$(title).click(function() {
$(content).toggle('fast', 'linear', function() {
$(this).css('display') === 'none'
? $(title).removeClass('is-active')
: $(title).addClass('is-active')
})
})
}
}
const addCalendarTitle = () => {
$('#blog-calendar').prepend(`<div id="blog-calendar-title">部落格日曆</div>`)
}
const side = () => {
addCalendarTitle()
setTimeout(() => {
poll($('#blog-sidecolumn').length, sideItemToggle)
}, 0)
}
module.exports = side
- simple/index.js
唯一需要寫的樣板程式碼:
import './style/index.scss'
import AwesCnb from '@awescnb'
class Simple extends AwesCnb {
constructor() {
super()
super.init(this.init)
}
init() {
require('./build')()
require('./plugins')()
}
}
new Simple()
構建
- 運行
npm start
分別對首頁、隨筆詳情頁、標籤頁等調整 - 運行
npm run build
打包
皮膚 simple 所有樣式和邏輯加起來有 130+kb,沒有異常。如果想擁有更好的體驗可以將 css 分離,在文章開頭的配置介紹中提供了這個選項。最後推送上去就能在部落格園切換到新皮膚了。
鏈接
- 安裝 & 構建皮膚://guangzan.gitee.io/awescnb-docs/
- 程式碼倉庫: //gitee.com/guangzan/awescnb