改造自己的主题

系列 - 建立自己的博客

考虑以下几种场景

  • markdown 已经不能满足我了,我要用上更多的页面特效,比如嵌入在线视频、嵌入特定网站卡片等
  • 博客主题不支持一些外部服务,比如说新的评论系统、搜索系统,但是主题作者那边不更新,咋办
  • 博客主题在一些场景的使用下会有错误,但是主题作者那边迟迟不给修复,咋办

要实现这些效果,就涉及到代码层面上的调整了。虽然会耗费一些时间,但相信只要能打造出自己心仪的博客效果,这点成本不算什么的。

hugo 是无法直接在博客中添加 HTML 代码的,我测试过,发现经过渲染后这些内容都会消失。取而代之的,hugo 提供了一种名为shortcodes 的功能,简单来说就是自己定义的 HTML 片段,并可在博客文章中选择性地插入这个片段。

hugo 官方已经内置了一些 shortcodes,比如嵌入 YouTube 视频、嵌入 tweet 等。当然了,DoIt 主题也提供了很多的 shortcodes,比如嵌入哔哩哔哩视频、横幅特效、ECharts 图、音乐播放器等。

那如果希望添加更多的功能,或是对内置的 shortcodes 效果不满意呢,根据 hugo 的说法,是可以自定义的。简单来说,主题shortcodes 相关的文件路径为 themes\DoIt\layouts\shortcodes,用户的路径应该为 layouts\shortcodes,添加同名文件就能覆盖主题提供的,不同名文件则算作用户新增的。hugo 官方文档也提到,如果要保证不重名,可以再加上一层路径,例如 layouts\shortcodes\custom,之后在引用 shortcodes 也注意加上 custom\ 即可。

就拿我博客中的友链来说吧,其实这个就是自定义了一个相关的 shortcodes,并在一篇博客中引用了这个片段而已,这个还得多感谢雨临Lewis博客中提供的代码。先在 custom 路径下添加 friend.html 文件,内容如下

{{ if .IsNamedParams }}
{{ $defaultImg := "https://sdn.geekzu.org/avatar/d41d8cd98f00b204e9800998ecf8427e?d=retro" }}
<a target="_blank" href={{ .Get "url" }} title={{ .Get "name" }}---{{ .Get "word" }} class="friend url">
    <div class="friend block whole {{ .Get " primary-color" | default "default"}} {{ .Get "border-animation" | default "shadow"}}">
        <div class="friend block left">
            <img class="friend logo {{ .Get " img-animation" | default "rotate"}}" src={{ .Get "logo" }} onerror="this.src='{{ $defaultImg }}'" />
        </div>
        <div class="friend block right">
            <div class="friend name">{{ .Get "name" }}</div>
            <div class="friend info">"{{ .Get "word" }}"</div>
        </div>
    </div>
</a>
{{ end }}

然后需要添加样式代码,建立文件 assets/css/_custom.scss,然后往其中加入 scss 代码,如下

.friend.url {
    text-decoration: none !important;
    color: black;
}

.friend.logo {
    width: 56px !important;
    height: 56px !important;
    border-radius: 50%;
    border: 1px solid #ddd;
    padding: 2px;
    margin-top: 14px !important;
    margin-left: 14px !important;
    background-color: #fff;
}

.friend.block.whole {
    height: 92px;
    margin-top: 8px;
    margin-left: 4px;
    width: 31%;
    display: inline-flex !important;
    border-radius: 5px;
    background: rgba(14, 220, 220, 0.15);

    &.shadow {
        margin-right: 4px;
        box-shadow: 4px 4px 2px 1px rgba(0, 0, 255, 0.2);
    }
    &.borderFlash {
        border-width: 3.5px;
        border-style: solid;
        animation: borderFlash 2s infinite alternate;
    }
    &.led {
        animation: led 3s infinite alternate;
    }
    &.bln {
        animation: bln 3s infinite alternate;
    }
}

.friend.block.whole {
    &:hover {
        color: white;
        & .friend.info {
            color: white;
        }
    }

    &.default {
        --primary-color: #215bb3bf;
        &:hover {
            background: rgba(33, 91, 179, 0.75);
        }
    }
    &.red {
        --primary-color: #e72638;
        &:hover {
            background: rgba(231, 38, 56, 0.75);
        }
    }
    &.green {
        --primary-color: #2ec58d;
        &:hover {
            background: rgba(21, 167, 33, 0.75);
        }
    }
    &.blue {
        --primary-color: #2575fc;
        &:hover {
            background: rgba(37, 117, 252, 0.75);
        }
    }
    &.linear-red {
        --primary-color: #e72638;
        &:hover {
            background: linear-gradient(to right, #f9cdcd 0, #e72638 35%);
        }
    }
    &.linear-green {
        --primary-color: #2ec58d;
        &:hover {
            background: linear-gradient(to right, #1d7544 0, #2ec58d 35%);
        }
    }
    &.linear-blue {
        --primary-color: #2575fc;
        &:hover {
            background: linear-gradient(to right, #6a11cb 0, #2575fc 35%);
        }
    }
}

.friend.block.whole .friend.block.left img {
    &.auto_rotate_left {
        animation: auto_rotate_left 3s linear infinite;
    }
    &.auto_rotate_right {
        animation: auto_rotate_right 3s linear infinite;
    }
}
.friend.block.whole:hover .friend.block.left img {
    &.rotate {
        transition: 0.9s !important;
        -webkit-transition: 0.9s !important;
        -moz-transition: 0.9s !important;
        -o-transition: 0.9s !important;
        -ms-transition: 0.9s !important;
        transform: rotate(360deg) !important;
        -webkit-transform: rotate(360deg) !important;
        -moz-transform: rotate(360deg) !important;
        -o-transform: rotate(360deg) !important;
        -ms-transform: rotate(360deg) !important;
    }
}

.friend.block.left {
    width: 92px;
    min-width: 92px;
    float: left;
}

.friend.block.left {
    margin-right: 2px;
}

.friend.block.right {
    margin-top: 18px;
    margin-right: 18px;
}

.friend.name {
    overflow: hidden;
    font-weight: bolder;
    word-wrap:break-word;
    word-break: break-all;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 1;
    -webkit-box-orient: vertical;
}

.friend.info {
    margin-top: 3px;
    overflow: hidden;
    word-wrap:break-word;
    word-break: break-all;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    line-height: normal;
    font-size: 0.8rem;
    color: #7a7a7a;
}

@media screen and (max-width: 900px) {
    .friend.info {
        display: none;
    }
    .friend.block.whole {
        width: 45%;
    }
    .friend.block.left {
        width: 84px;
        margin-left: 15px;
    }
    .friend.block.right {
        height: 100%;
        margin: auto;
        display: flex;
        align-items: center;
        justify-content: center;
    }
    .friend.name {
        font-size: 14px;
    }
}

@keyframes bln {
	0% {
		box-shadow: 0 0 5px grey,inset 0 0 5px grey,0 1px 0 grey;
		box-shadow: 0 0 5px var(--primary-color,grey),inset 0 0 5px var(--primary-color,grey),0 1px 0 var(--primary-color,grey)
	}

	to {
		box-shadow: 0 0 16px grey,inset 0 0 8px grey,0 1px 0 grey;
		box-shadow: 0 0 16px var(--primary-color,grey),inset 0 0 8px var(--primary-color,grey),0 1px 0 var(--primary-color,grey)
	}
}

@keyframes led {
	0% {
		box-shadow: 0 0 4px #ca00ff
	}

	25% {
		box-shadow: 0 0 16px #00b5e5
	}

	50% {
		box-shadow: 0 0 4px #00f
	}

	75% {
		box-shadow: 0 0 16px #b1da21
	}

	to {
		box-shadow: 0 0 4px red
	}
}

@keyframes borderFlash {
	0% {
		border-color: white;
	}

	to {
		border-color: grey;
		border-color: var(--primary-color,grey)
	}
}

@keyframes auto_rotate_left {
	0% {
		transform: rotate(0)
	}

	to {
		transform: rotate(-1turn)
	}
}

@keyframes auto_rotate_right {
	0% {
		transform: rotate(0)
	}

	to {
		transform: rotate(1turn)
	}
}

使用时,只需要在博客文章中加入下列内容,如图所示

./assets/image-20240226220224341.png

上面只是在特定文章中选择性地添加某些页面效果,如果希望让所有页面都有某种效果呢?比如在底部添加一个即时计算网站运行时间的文本,这个效果 DoIt 主题是不支持的。

要实现这类效果的话,就需要引入自定义的前端代码。这里先补充下 hugo 覆盖主题文件的做法吧,稍后要用到。要明确的是,主题代码路径位于 themes\DoIt\下,想覆盖主题代码,就得在博客根目录下新建同样的路径,并把主题代码文件复制过来,然后在这个新文件上进行修改。这样最后博客运行时,加载的会是自己修改过的主题代码。举个例子,主题菜单栏的代码文件路径是 themes/DoIt/layouts/partials/header.html,那么要新建的文件路径应该为 layouts/partials/header.html

对于网站页面样式代码 CSS,这个 DoIt 主题已经提供了一种引入方式了,即在根路径下建立文件,路径为 assets/css/_custom.scss,然后往里面写入代码即可。至于 JavaScript 代码和 HTML 代码,我这边暂时没找到官方提供的修改入口。

再次感谢雨临Lewis博客提供的思路,我终于明白了该如何引入各种前端代码。先说下思路吧,打开网页源代码检索<script type="text/javascript",发现大多集中在一个 <div class="assets"> 的块中,所以猜想只要在块中最末尾引入自己需要的 JavaScript 文件即可。然后在项目中检索<div class="assets">,发现位于 themes/DoIt/layouts/partials/assets.html 这个文件中,那么只需要去覆盖这个文件,然后往里面加入引入 JavaScript 代码文件的命令即可。这是 DoIt 主题的做法,对于其它主题,可参考这个思路,看看能不能找到框架的脚本文件引入入口。

说下做法吧,就是添加自己的 JavaScript 文件,路径是 static/js/custom.js,然后在复制文件 assets.html<div class="assets"> 代码块末尾加入文件引入命令,代码如下

<div class="assets">    
    {{- range (.Scratch.Get "this").style -}}
        {{- partial "plugin/style.html" . -}}
    {{- end -}}

    {{- range (.Scratch.Get "this").configScript -}}
        {{- partial "plugin/script.html" . -}}
    {{- end -}}

    {{- range (.Scratch.Get "this").script -}}
        {{- partial "plugin/script.html" . -}}
    {{- end -}}
    
    {{- partial "plugin/analytics.html" . -}}

    {{- /* 自定义的js文件 */ -}}
    <script type="text/javascript" src="/js/custom.js"></script>
</div>

同理,也可以引入自定义的 CSS 样式文件。另外,对于 HTML 代码,我发现在<div class="assets">这个元素块直接添加就行,比如

<div id="mycustomelement"></div>

<div class="assets"> 

而且由于<div class="assets"> 这个代码块是位于源代码底部的,所以里面引入文件的效果优先级是最高的。

这样一来,后续想引入任何新出的外部服务,应该都是能够做到了。

对了,其实也可以用这种方式修复主题存在的部分问题。比如之前在部署博客这篇文章里提到过,DoIt 主题对于 waline 评论系统的安全特性支持不够,导致 recaptchaV3 按照文档配置后依旧无法生效。之前是去覆盖主题评论部分代码来让 recaptchaV3 生效,既然知道了这种方式,就可以有另外一种更方便的修改方式了,即在自定义 JavaScript 文件中加入如下代码,以关闭表情包搜索为例(原主题也是不支持的),经过测试是通过的。

if (window.config && window.config.comment && window.config.comment.waline) {
    window.config.comment.waline.search = false;
}

如果添加自定义前端代码文件后,发现刷新页面没效果,可以尝试清理网页缓存,步骤是

  • 调出网页控制台,比如 chrome 是按 F12
  • 右键网址输入框左边的刷新按钮,选择清空缓存并硬性重新加载

这种方式其实是最麻烦的,不仅需要了解 HTML、CSS、JavaScript 这三种语言的用法,还得分析主题代码的逻辑,然后去修改对应位置的代码,个人还是更推荐通过引入自定义的前端代码去进行调整。

能说的就是大致的修改思路以及一些成功案例吧,内容待以后添加。。。

相关内容