zoukankan      html  css  js  c++  java
  • 构建一个简约博皮的过程

    前置

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

    准备工作

    配置

    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 = $(
            `<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="https://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
    
    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 打包

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

    链接

  • 相关阅读:
    SpringMVC + spring3.1.1 + hibernate4.1.0 集成及常见问题总结
    开涛spring3(9.4)
    开涛spring3(9.3)
    开涛spring3(9.2)
    开涛spring3(9.1)
    开涛spring3(8.4)
    分水岭算法——学习笔记
    【代码备份】pocs.m
    【代码备份】NLM插值
    【代码备份】原图降采样后进行NLM滤波
  • 原文地址:https://www.cnblogs.com/guangzan/p/12979619.html
Copyright © 2011-2022 走看看