emoji 即表情符号,来自日语词汇“絵文字”(假名为“えもじ”,读音即 emoji),最初由栗田穰崇创作,目前已被纳入 Unicode 编码并得到广泛支持。⭐
Emojipedia 收录了目前所有的 emoji。🎈
尽管如此,emoji 在部分老设备、旧系统的支持性并不好,甚至可能显示为类似“□”的无法显示字符。这当然是不好的。为了优化展现,目前常用的做法是渲染时用图片替换 emoji 字符。相关的项目有 GitHub 正在使用的 g-emoji-element 以及 Twitter 的 twemoji 等。
本文将要介绍的就是引入 twemoji 来优化展现。🍻
twemoji 官方默认提供 MaxCDN 作为加载源,但该 CDN 在国内似乎可用性并不好。因此,笔者还是一贯地选择 jsDelivr。当然,你也可以从官方仓库下载并制作你自己的源。
twemoji.js 从 NPM 拉取,使用:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/twemoji.min.js"></script>将上述代码插入到网页中适当的位置,即可引入 twemoji。此时 window 下会有 twemoji 对象以便调用。💡
twemoji.parse() 方法的详细用法在 README 中有很科学💎的说明,所以笔者在这里只介绍整体替换的思路。
这个方法接受两个参数:第一个参数是解析的对象,直接使用 document.body 即可;第二个参数是一个 Object,包含一些选项:
{
callback: Function, // default the common replacer
attributes: Function, // default returns {}
base: string, // default MaxCDN
ext: string, // default ".png"
className: string, // default "emoji"
size: string|number, // default "72x72"
folder: string // in case it's specified
// it replaces .size info, if any
}callback 是每次替换的回调,返回值将作为 <img> 的 src 属性,即对应替换图片的 URL。attributes 是另一个回调,返回值应该是一个 Object,这个 Object 将被整合到 img 的 attributes,例如返回 {test: 'test'} 就会使 <img> 多出一个 test="test" 的属性。base 是 emoji 替换图片的加载源,默认是 MaxCDN,稍后笔者将会给出 jsDelivr 的对应参数值。⚠️ext 指定 emoji 替换图片的扩展名,默认是 .png,也可以使用 .svg 或其他类型(需要 size 或 folder 配合)。className 指定 <img> 的 class 属性,默认是 emoji,如果与现有 class 有冲突则需要改变。size 指定“图片尺寸”,实质上是指定一个目录名。不建议修改。folder 指定 emoji 替换图片的目录名,将会覆盖 size 的设置。⚠️ 使用 jsDelivr 时,emoji 替换图片不能直接从 NPM 拉取,而是要从 GitHub 拉取,应该把 base 设置为 https://cdn.jsdelivr.net/gh/twitter/[email protected]/assets/(末尾的 / 不可省略,原因后述)。
如果没有指定 callback,组装 URL 的规则默认是 {base}{size|folder}/{icon}{ext},其中 icon 作为 callback 的第一个参数传入。所以,当使用默认 callback 时,如果 base 的末尾缺少 /,会直接导致 URL 拼接非法。同理,ext 也一定要以 . 开头。👌
综上,解析的代码如下:
twemoji.parse(document.body, {
base: 'https://cdn.jsdelivr.net/gh/twitter/[email protected]/assets/'
});handsome 是 Typecho 主题,所以首先需要确保 Typecho 已经开启 emoji 支持,即数据库编码已经调整至 utf8mb4,否则可能会出现数据损坏的严重后果。调整的方法可以参考:使 Typecho 支持 emoji 的显示😂😂😂 以及 如何让Typecho支持emoji的存储。调整完毕后,可以发送几条包含 emoji 的评论测试配置是否有效。
handsome 主题配置中可以直接加入 twemoji 的设定:
第一步,依次展开 开发者设置 -> 自定义输出body 尾部的HTML代码,在末尾加入:
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/twemoji.min.js"></script>第二步,依次展开 开发者设置 -> 自定义 JavaScript,同样在末尾加入:
$(function() {
twemoji.parse(document.body, {
attributes() {return {nogallery: 'nogallery'};},
base: 'https://cdn.jsdelivr.net/gh/twitter/[email protected]/assets/'
});
});第三步,依次展开 开发者设置 -> 自定义 CSS,还是在末尾加入:
img.emoji {
height: 1em !important;
width: 1em !important;
margin: 0 .05em 0 .1em !important;
vertical-align: -0.1em !important;
border-radius: 0 !important;
border: 0 !important;
display: inline-block !important;
padding: 0 !important;
}为了处理样式冲突,不得不加了很多 !important(话说这也太多了吧😂)
如果你还开启了 PJAX,那么在 PJAX -> PJAX回调函数 末尾也需要加入第二步中的代码。
这样,我们就成功引入了 twemoji,emoji 在旧设备、旧系统也能很漂亮了~💯
]]>cdn.mathjax.org,在速度上有较大提升(国内尤为明显)。咕咕咕了很久之后我终于下定决心将其更新到了 v3 版本。这篇笔记就来讲解一下从 v2 升级到 v3 的一些快捷方法和注意事项。[scode type="blue"]这篇笔记针对 Typecho 的 handsome 主题编写,对于其他程序/主题的 MathJax 更新可能仅仅只有启发作用。[/scode]
MathJax 有一个配置选项,在 v2 系列版本中书写在 <script type="text/x-mathjax-config"> 标记中,例如 handsome 的默认配置是:
MathJax.Hub.Config({messageStyle: "none",tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]}});但在 v3 版本中,上述配置格式不再被支持。好在我们可以使用 MathJax 官方推出的工具 MathJax Configuration Converter 来快速生成对应的 v3 配置。
我生成的配置是:
window.MathJax = {
tex: {
inlineMath: [['$','$'], ['\\(','\\)']],
autoload: {
color: [],
colorV2: ['color']
},
packages: {'[+]': ['noerrors']}
},
svg: {
fontCache: 'global'
},
options: {
ignoreHtmlClass: 'tex2jax_ignore',
processHtmlClass: 'tex2jax_process'
},
loader: {
load: ['[tex]/noerrors']
}
};笔者使用的 handsome 主题在 pjax 时有一次 MathJax.Hub.Queue(["Typeset",MathJax.Hub,"body"]); 的操作,这种写法不再被 MathJax 支持了,于是笔者参考 Typesetting and Converting Mathematics — MathJax 3.0 documentation 将其重写。重写后实际上更短了:
MathJax.typeset();简单直接,暴力美学。
如果你的调用更加复杂,请尽可能详细阅读文档再对应作出修改。
Converter 生成的整个配置是可以直接复制并插入 body 的末尾的,例如一个类似这样的东西:
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-svg.js" id="MathJax-script"></script>我的写法是:
<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-svg.js" async></script>并没有什么明显的区别。插入部署完毕后,来到前端页面 F12 并在 Console 中运行 MathJax.version,就可以看到 3.x.x 的返回值咯 ~
什么是“黑幕效果”呢?
[heimu]像这样[/heimu],正常状态下被一个黑条遮挡,只有当鼠标放在上面(移动端是长按)后才显示出内容的,富有交互性的有趣功能样式,就被成为“黑幕效果”。它可以让文章更加活泼,内容更加有趣,[heimu]以及表达一些特殊的语气~[/heimu]
这样,你就对黑幕效果有了一个基本的认知[heimu],阅读气氛也活跃起来了[/heimu],下面我们就来看一看小黑条的背后有哪些核心技术。 ::majsoul:028::
虽然大家都知道了,但还是简单提一句,CSS(Cascading Style Sheets,层叠样式表)是现代互联网技术中一个重要的组成部分,在前端开发中有着至关重要的地位。通过这种东西,我们可以方便地为网页中的内容添加丰富多彩的样式。
黑幕效果需要哪些样式呢? ::majsoul:034::
我们再来看一下黑幕效果的呈现:[heimu]我是小黑条~[/heimu]
它的背景是黑色的,文字颜色与背景相同,所以正常状态下看不见文字内容。背景颜色在CSS中通过background-color属性控制,而前景色(也就是文字颜色啦)通过color属性控制,为了方便,我们定义一个.heimu类,这样所有class="heimu"的元素都可以自动获得我们规定的这些属性。就像这样:(现在请暂时不要记笔记,需要记笔记的时候我会提醒你哦)
.heimu {
background-color: #000;
color: #000;
}这个东西怎么用呢?我们写一个测试页面看看:
<!doctype html>
<html>
<head>
<title>heimu test</title>
<style>
.heimu {
background-color: #000;
color: #000;
}
</style>
</head>
<body>
<p>This is <span class="heimu">heimu text</span>.</p>
</body>
</html>
(蓝色部分是我用鼠标选中造成的)还行,有那种感觉了,但是黑幕效果怎么变成了刮刮乐啊?
这是因为我们还没有处理鼠标移入(或移动端长按)时这个样式的变化。这部分我们使用:hover伪类实现,这个伪类将会在鼠标放上去的时候起作用,改变样式。
.heimu:hover, .heimu:active {
color: #FFF;
}实际使用时,我还使用了:active伪类,但它在此处作用不明显[heimu],仅仅是我出于心理安慰加上的[/heimu]。在测试页面中加入这部分内容,我们就基本完成啦~
实际使用时,不一定要用完完全全的黑色,只需要前景色与背景色一致即可;我们还可以加入渐变(用transition实现)。下面放出我使用的CSS部分内容,同学们可以记笔记啦~
.heimu:active, .heimu:hover {
color: #fff
}
.heimu {
background-color: #252525;
color: #252525;
text-shadow: none;
transition: color 0.3s;
}通过前面的样式准备工作,我们已经可以通过<span class="heimu" title="你知道的太多了">小黑条~</span>这样的方式来调用我们的黑幕效果了,但是这样未免冗长,而且大部分都是重复内容。
我们规定一个自定义语法[heimu]xxx[/heimu],把一对这样的闭合标记解析为<span class="heimu" title="你知道的太多了">xxx</span>,中间内容不变,这样就可以大大降低我们调用时的工作量。这个工作可以交给插件实现,也可以交给js实现。出于性能考虑,我用js写了一个不算完整的实现。
用js进行替换的原理是正则匹配,在整个文章内容中按照DOM树遍历一次,完成整个替换。之所以没有直接替换是因为要规避一些特殊标记内的关键文本,例如数学公式和<code>元素。这样同时带来了一个缺陷,即黑幕效果只能覆盖简单文本,复杂文本(例如有链接的情况)当前版本是无法解析的。这里直接给出代码如下:
function icore_init() {
var t = new RegExp("\\[heimu\\](.*?)\\[/heimu\\]","g");
$("div.entry-content,div#morphing-content>div.page").children("p,blockquote,div.tip").each(function() {
var n = "";
$(this).contents().each(function() {
var i = this.outerHTML;
null == i && (i = $(this).text()),
null != i && ((1 === this.nodeType || 3 === this.nodeType) && "IMG" != this.tagName && "PRE" != this.tagName && "CODE" != this.tagName && "SCRIPT" != this.tagName && "BR" != this.tagName ? -1 != $.inArray("light-link", this.classList) || function(t=this.classList) {
if (null == t || null == t)
return !1;
for (var n = 0; n < t.length; n++)
if (-1 != t[n].indexOf("MathJax"))
return !0;
return !1
}() ? n += i : (i = i.replace(t, function() {
return '<span class="heimu" title="\u4f60\u77e5\u9053\u7684\u592a\u591a\u4e86">' + arguments[1] + "</span>"
}),
n += i) : n += i)
}),
$(this).html(n)
});
}
icore_init();[heimu]可以看到内容极其紊乱,其实是我混淆后找不到原文件了(悲[/heimu]
只对handsome主题5.x系列版本做了适配,不保证不出现未知问题,如有情况可以评论联系我,但不保证解决。
如果开启pjax,请在pjax回调函数中加入icore_init();,否则无刷新加载后不会进行解析。(handsome主题的该配置项位于“PJAX->PJAX回调函数”中,如果其中已经有代码,加入在末尾即可)
title属性可以自定义,该属性规定的是鼠标放上去后显示的文本。
至此,黑幕效果背后的黑科技已经完全呈现在你眼前了~ ::majsoul:077::
顺便说一句,这部分内容也包括在我的自定义增强效果iCore(不过暂时没有开源,因为内容太少了)中哦~ ::majsoul:102::
OwO.json都是完全由自己来完成,不像上次扩展贴吧表情的时候有现成的东西可以直接拿来用,所以记录一下以便之后需要时留作参考。 ::majsoul:001:: 并没有尝试一组表情中能否多种格式图片并存,这次统一采用了png格式,避免给自己找锅。
本次新增的一组表情共计151张,需要在OwO.json中对它们一一进行声明。手写是不太可能的,因为是大量的重复工作,懒惰如我显然是宁可写一个Generator也不会去一条条加。gen.py的内容如下:
#!/usr/bin/python3
OWO_NAME='雀魂'
OWO_SLUG='majsoul'
PICTURE_COUNT=151
f=open("OwO.json","w",encoding='utf-8')
f.write(''' "%s": {
"name": "%s",
"type": "image",
"container": [
'''%(OWO_NAME,OWO_SLUG))
for i in range(1,PICTURE_COUNT+1):
f.write(''' {
"icon": "%03d",
"text": "%03d"
}'''%(i,i))
if i<PICTURE_COUNT:
f.write(',')
f.write('\n')
f.write(''' ]
}''')
f.close()其中OWO_NAME为这组表情的显示名称,OWO_SLUG为表情的内部别名,PICTURE_COUNT为图片总数。若需要生成其他表情包声明,改动这些值即可。
执行后将在当前目录下生成一个OwO.json,内容是需要插入到完整OwO.json中的这个表情包的声明。 ::majsoul:029::
[scode type="yellow"]这里生成的并不是完整的json文件,需要手动将其插入完整的OwO.json中。[/scode]
如果执行正确,将会生成形如下方代码块中所示的一段文本:
"雀魂": {
"name": "majsoul",
"type": "image",
"container": [
{
"icon": "001",
"text": "001"
},
{
"icon": "002",
"text": "002"
}
]
}为方便起见,我只列出了PICTURE_COUNT=2的情况。
插入位置如下图所示,需要视情况在后方补一个半角逗号。

应用json检测工具检测通过后即可部署。
由于上述代码是针对Python3编写的,考虑到有时并不方便获取Python3执行环境,补充一个JavaScript版本:
(function(){
'use strict';
const OWO_NAME="雀魂";
const OWO_SLUG="majsoul";
const PICTURE_COUNT=151;
const html_encode=str=>{
let s="";
if (str.length==0)return"";
s=str.replace(/&/g,"&");
s=s.replace(/</g,"<");
s=s.replace(/>/g,">");
s=s.replace(/ /g," ");
s=s.replace(/\'/g,"'");
s=s.replace(/\"/g,""");
s=s.replace(/\n/g,"<br/>");
return s;
}
let output=` "${OWO_NAME}": {
"name": "${OWO_SLUG}",
"type": "image",
"container": [
`;
for(let i=1,num;i<=PICTURE_COUNT;i++){
num=(Array(3).join('0')+i).slice(-3);
output+=` {
"icon": "${num}",
"text": "${num}"
}`;
if(i<PICTURE_COUNT)output+=',';
output+='\n';
}
output+=` ]
}`;
document.write(`<code>${html_encode(output)}</code>`);
})();在浏览器中新建标签页,按下F12键,在开发人员工具的Console选项卡中粘贴并执行上述代码即可生成在网页上,复制后插入即可。

当然是不可能的。
我使用的handsome主题自带lazyload功能,但是默认关闭,且作者并不推荐使用,称或将带来卡顿(?)。
当然,即使你的网站没有lazyload,你也可以自行引入,这里不再赘述。
一个可以实现图片懒加载的插件。所谓图片懒加载,即“你不看我就不加载”,最经典的操作便是监听一个scroll事件,当『快要看到这张图片』时,开始一个异步请求进行图片的加载。不难发现,其中最重要,也最微妙的就是对于『快要看到这张图片』时机的把握。
大部分经典的实例,是通过一个距离来控制,也就是lazyload中的threshold参数。它用于设置一个临界值,当屏幕下端距离屏幕外的图片距离小于等于这个值的时候,lazyload开始load。大概类似于早自习犯困的你拜托同桌当老师来的时候提醒你一下(addEventListener),当负责任又好心的同桌提醒你时(trigger),你就赶紧爬起来认真早读。
我注意到,当网络状况差,图片无法立即加载时,大部分关键的js都处于蓄势待发状态,需等待图片加载完成(此时才会触发document的ready)才会开始执行,极大地影响了用户[heimu]我自己[/heimu]体验。事实上,图片加不加载好对我js的执行并没有影响,完全可以让图片加载自个在后面慢慢拖,而让document的ready提前,从而尽可能缩短js不执行的糟糕真空期。
这时,就轮到lazyload大显身手啦~
使用handsome主题的typecho用户可以通过打开“后台->控制台->外观->设置外观->主题增强功能->增强功能开关”中的“延迟加载图片(lazyload)”选项来打开lazyload。

通用的方法是在body结束前、jQuery加载后手动引入lazyload.js
<script src="https://cdn.jsdelivr.net/npm/[email protected]/jquery.lazyload.min.js"></script>同时对需要进行lazyload的元素属性作一定的调整,设置data-original为实际加载地址,并设置一个占位图片,在这里不再赘述。
在使用过程中,我发现了新的问题,即我的图片加载实在是太 慢 了,以至于根本无法在滚动到图片位置时完成加载。就好像虽然你的同桌提醒了你,但你实在是太困,一直磨蹭到老师站在你面前了也没把书拿出来。
事实上,我们也不是真的希望这个图片“懒”,只是希望它不要给大家拖后腿,别卡住document的ready事件。
那么我们就完全可以跳出常规思路,不再使用默认的scroll作为触发事件。lazyload提供了一个参数event允许我们自定义触发事件。只需要给这个参数随便传一个不冲突的事件名字,在设置完成lazyload之后立即trigger我们的自定义事件,即可瞬间开始图片的异步请求,而不必等待滚动,如此一来就能进一步优化体验。
栗子:
$("img").lazyload({
effect: "fadeIn",
event: "display" // 替换默认的触发事件
});
$("img").trigger("display"); // 立即触发仅仅是这样似乎还不够,lazyload似乎仍然是滚动优先触发。虽然做到这个程度已经差不多了,但我不想让它的触发机制复杂起来,不妨设想有时需要点击后再开始加载,这时滚动触发就没法让它按照预期去呈现。所以我们再给它补一个threshold参数混淆视听,设置一个绝对值很大的负数,滚动触发基本上就没什么戏了。这里我选择了-0x7fffffff,下面是我最终使用的代码:
$("img").lazyload({
threshold: -0x7fffffff, // 让不需要的部分见鬼去
effect: "fadeIn",
event: "display" // 替换默认的触发事件
});
$("img").trigger("display"); // 立即触发
]]>