使用SVG Sprites(雪碧图)实现本站表情的解决方案

尚寂新
尚寂新
2024/06/07 23:39

起初,发现自己用的酷安表情包,当时为了极致占用(为了降低网络开销),已经被压缩的非常糊。然后,就想到是否能用SVG来解决这个问题,于是,找到位大佬在Github仓库里提供的SVG格式的酷安表情包,故经过筛选之后取之,基本只留下了圆脸和狗头表情,其他的都略去了。最终筛选出这80张表情。

筛选出来的酷安表情
筛选出来的酷安表情

正当因如何再次缩减表情数量(减少HTTP请求次数)而犯愁的我,经过搜索查找,找到了SVG Sprites(雪碧图)的方案,可以把数个SVG给合并成一个文件,然后通过use来调用被合并成一个.svg文件中所对应的symbol,这样只需要控制合并之后的SVG文件大小就可以了,而且还可以做到随用随加载,且HTTP请求只有一次。这种方案非常满足我的需要,那么就....开搞!

如何合并

找个能做雪碧图的工具(我这次用的是SVG Sprites Generator),把需要的图全部上传之后,会因图片的数量以及大小等因素,需要等它生成一会儿。生成完毕后,把左栏的symbol区全都复制下来,粘贴到个空文件里面,另存为.svg(比如说在Windows中,新建个txt文档,然后把左列生成的SVG文本粘贴进去,保存,然后再改后缀名为.svg)。右栏的use井号后面的文本,就是原先每个SVG文件的文件名。这样雪碧图就已经是做完了。

雪碧图工具生成相关介绍
雪碧图工具生成相关介绍

如果你的表情文件有"haha.svg"、"huaji.svg"、"doge.svg"的话,后续使用use调用合并出的雪碧图,xlink:href的值就是"路径/雪碧图文件名.svg#haha"、"路径/雪碧图文件名.svg#huaji"、"路径/雪碧图文件名.svg#doge"。故在合成雪碧图之前,一定要改文件名为自己喜欢的!(合成雪碧图之前的文件名可以使用中文。)

用于Typecho

之前写过一篇关于本站的表情解析的解决方案,大体上还是用以前的代码,不过之前的代码,前端用的是jQuery实现的,现在把用原生JavaScript的代码和修改好的functions.php文件的函数部分也重新在下面给一下,方便大家效仿。

// 主题的 functions.php 文件
function parseCommentText($obj){
    $pattern = '/@\[(.*?)\]/is';
    $obj = preg_replace_callback($pattern, 'forEmotionReplace', $obj);
    return trim($obj);
}

function forEmotionReplace($matches){
    return '<svg class="emoji-set"><use xlink:href="你的SVG文件路径.svg#'.htmlspecialchars($matches[1]).'"></use></svg>';
}

注:parseCommentText的调用方法:在comments.php或者是自己的主题的自定义评论结构中的$comments->content,改为parseCommentText($comments->content);即可完成调用。

再注:上述代码中$matches到的文本,即为合成雪碧图之前的每个小表情文件的文件名。

前端部分的关键代码(具体表情框需要怎么实现,取决于自己主题):

const emotions = [
    '表情名1', '表情名2'
];

// emotions 里填合成雪碧图之前,每个小表情的文件名,不要文件后缀,直接文件名即可

const owoList = document.getElementById('表情选择框');

emotions.forEach((emotion) => {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    svg.setAttribute("class", "emoji");
    svg.setAttribute("alt", `@防止被解析用时请删这里的中文[${emotion}]`);
    svg.setAttribute("title", emotion);

    const use = document.createElementNS("http://www.w3.org/2000/svg", "use");
    use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", `你的表情文件路径.svg#${emotion}`);
    svg.appendChild(use);

    owoList.appendChild(svg);

    svg.addEventListener('click', () => {
        insertAtCaret(document.querySelector('此处需要指定你评论框的textarea'), svg.getAttribute("alt"));
    });
});

insertAtCaret方法:

const insertAtCaret = (textarea, text) => {
    const cursorPos = textarea.selectionStart;
    const front = textarea.value.substring(0, cursorPos);
    const end = textarea.value.substring(cursorPos);

    textarea.value = front + text + end;
    textarea.selectionStart = cursorPos + text.length;
    textarea.selectionEnd = textarea.selectionStart;
    textarea.focus();
}

剩下的,就是调整CSS,调整一下如何实现表情框,就基本可以了。

另附:简单的伸缩表情栏的DEMO:点我到CodePen查看此DEMO

<button onclick="btnOnclick();">点我出表情栏</button>
<div id="emojiContainer" style="display: none;">省略插入表情的逻辑</div>
<script>
    const btnOnclick = () => {
        const emojiContainer = document.getElementById('emojiContainer');

        if (emojiContainer.style.display === 'none') {
            emojiContainer.style.display = 'block';
        } else {
            emojiContainer.style.display = 'none';
        }
    };
</script>

后注:一定要自己通过CSS指定表情的宽高,否则表情图有很大概率会因为尺寸过大而被顶出去,造成一种看不到表情的效果(向SVG标签上的class上追加样式即可)。.emoji是表情选择框的样式,.emoji-set是评论列表的渲染样式,之所以把这两个分开,是为了更方便写样式,比如说让表情选择框里的表情更大一些,或者是有一些点击样式什么的。下面给出两个合在一起写的基础CSS,具体如下:

.emoji-set,
.emoji {
    height: 20px;
    width: 20px;
    vertical-align: text-bottom;
}
已有 2 条评论 (旧评论在前)
  1. 泽泽 友链
    回复
    2024-06-08 17:21 辽宁省 macOS

    不错哦很高清,看了下体积好像700k,好像还是纯图片体积小点

    1. 尚寂新 博主
      回复
      2024-06-08 17:33 黑龙江省 Windows NT10

      所以说这种方案还是很不错的

      不对...这700kb里面含80张图,平均下来每张图不到10kb

添加新评论 (Markdown Supported)
(ノ°ο°)ノ