构建一个简约博皮的过程

前置

由于之前构建的皮肤 reacg 偏二次元风,尽管提供了大量配置(包括几乎任何颜色、插件等的配置),依然有人吐槽花里胡哨,遂重新构建了一款简约风格的博客园皮肤, 正如你所见。下文我将从零介绍它的构建过程,构建它最快花费一个小时到几个小时。由于之前做了大量工作,所以现在按照流程走一遍就完事了。

准备工作

  • 环境:node & npm
  • git clone https://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,构建完成直接推送到远端,直接切换线上版本

开始

  1. simple/style 下存放样式文件
  • index.scss
  • index.m.scss
  • markdown.scss
  • reset.scss
  • tools.scss

最终只在入口文件 index.js 中导入 index.scss,其他 scss 由 index.scss 引入,由于之前编好了样式代码,所以需要新编写的样式极少。

  1. 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 样式代码高亮
  • 代码行号
  • 图片灯箱
  • 显示评论区头像
  • 捐增二维码
  • 按钮工具条(返回顶部,推荐,收藏。。。)
  • 评论框表情
  • 播放器
  • 在文章头部构建文章信息
  • 构建文章签名信息
  • 文章头图
  • 通知功能

由于这个皮肤的基调是简约,所以只引入一些常用的功能模块。花里胡哨的就不考虑了。

  1. 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 = $(
        `

目录

`, ) const $ulContainer = $('
    ') 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 = `
  • ${text}
  • ` $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(``)
        $('#blog_nav_newpost').appendTo('.navbar-end')
        $(
            `构建新皮肤`,
        ).appendTo('.navbar-end')
        $(`开源主页`).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(`${title}`)
        $('head').append(
            ``,
        )
    }
    
    // header移动端菜单
    const headerBtn = () => {
        const ele = ``
        $('#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(``)
        $('#blogTitle h2').after(``)
        // github icon
        if (github.enable) {
            $('.custom-links').append(``)
        }
        // qq
        // $('.custom-links').append(``)
        // 知乎
        $('.custom-links').append(``)
    }
    
    // 首页 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(`
    博客日历
    `) } const side = () => { addCalendarTitle() setTimeout(() => { poll($('#blog-sidecolumn').length, sideItemToggle) }, 0) } module.exports = side
    1. 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 打包

    构建一个简约博皮的过程_第1张图片

    皮肤 simple 所有样式和逻辑加起来有 130+kb,没有异常。如果想拥有更好的体验可以将 css 分离,在文章开头的配置介绍中提供了这个选项。最后推送上去就能在博客园切换到新皮肤了。

    链接

    • 安装 & 构建皮肤:https://guangzan.gitee.io/awescnb-docs/
    • 代码仓库: https://gitee.com/guangzan/awescnb

    你可能感兴趣的:(构建一个简约博皮的过程)