前言
给 Twikoo 换一套表情,再加个鼠标悬停放大预览。表情列表和样式还在,放大脚本这次单独拆出来,后面维护也方便一些。
美化之前
本站使用 Hexo 6.0.0 + Butterfly 4.5.1,其他版本请按实际情况调整。样式、脚本引入及全局变量用法可参考:
参考链接
- Mo:Twikoo 添加自定义表情包。
- Leonus 的表情放大教程:
https://blog.leonus.cn/2022/owo-big.html(原链接暂不可用)。
- Heo:CSS 动画原作者,代码中保留署名。
修改方案
1. 准备表情 JSON
把表情数据保存到 source/static/json/OwO.json。下面从本站现有文件抽取了少量条目,格式不变;图片素材的使用仍需遵守各自来源要求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| { "颜文字": { "type": "emoticon", "container": [ { "icon": "OωO", "text": "Author: DIYgod" }, { "icon": "|´・ω・)ノ", "text": "Hi" } ] }, "Bilibili": { "type": "image", "container": [ { "icon": "<img src=\"https://owo.imaegoo.com/bilibili/6ea59c827c414b4a2955fe79e0f6fd3dcd515e24.png\">", "text": "tv_doge" }, { "icon": "<img src=\"https://owo.imaegoo.com/bilibili/a8111ad55953ef5e3be3327ef94eb4a39d535d06.png\">", "text": "tv_亲亲" } ] } }
|
emoticon 用于文字表情,image 的 icon 则是图片 HTML。只填写自己维护和信任的内容,不要把任意来访者提交的 HTML 直接拼进去。
部署后先确认 /static/json/OwO.json 可以直接返回 JSON,再在 Twikoo 管理设置的 EMOTION_CDN 填入自己站点这个文件的完整 URL。该设置属于后端管理配置,不能通过加一条 CSS 代替。
2. 添加样式
新建 source/static/css/owo-preview.css,添加下面的样式。如果之前改过这一块,记得先合并旧样式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #owo-big { position: fixed; align-items: center; background-color: rgb(255, 255, 255); border: 1px #aaa solid; border-radius: 10px; z-index: 9999; display: none; transform: translate(0, -105%); overflow: hidden; animation: owoIn 0.3s cubic-bezier(0.42, 0, 0.3, 1.11); }
.OwO .OwO-body .OwO-items .OwO-item:hover { box-shadow: 0 2px 3px 1px rgb(0 0 0 / 15%)!important; }
[data-theme=dark] #owo-big { background-color: #4a4a4a }
#owo-big img { width: 100%; }
@keyframes owoIn { 0% { transform: translate(0, -95%); opacity: 0; } 100% { transform: translate(0, -105%); opacity: 1; } }
|
然后在 _config.butterfly.yml 的 inject.head 里引入:
1 2 3
| inject: head: - <link rel="stylesheet" href="/static/css/owo-preview.css">
|
3. 恢复预览逻辑
旧实现通过 MutationObserver 等待表情 DOM,依赖具体的新增节点顺序。下面是保留“停留 300ms、放大约三倍”效果的这版代码,改为全局事件委托,只处理鼠标,不接管手机长按。
4. 添加脚本
新建 source/static/js/owo-preview.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| (() => { if (window.__juneOwoPreviewBound) return; window.__juneOwoPreviewBound = true; let timer; let box; const hide = () => { clearTimeout(timer); if (box) box.style.display = 'none'; }; document.addEventListener('pointerover', event => { if (event.pointerType !== 'mouse') return; const image = event.target.closest && event.target.closest('#twikoo-wrap .OwO-item img'); if (!image || image.contains(event.relatedTarget)) return; hide(); timer = setTimeout(() => { if (!image.isConnected) return; if (!box || !box.isConnected) { box = document.createElement('div'); box.id = 'owo-big'; box.style.pointerEvents = 'none'; document.body.appendChild(box); } const rect = image.getBoundingClientRect(); const width = Math.min(Math.max(rect.width * 3, 64), innerWidth - 20); const preview = document.createElement('img'); preview.src = image.currentSrc || image.src; preview.alt = image.alt || '表情预览'; box.replaceChildren(preview); box.style.cssText = 'display:block;position:fixed;pointer-events:none;width:' + width + 'px;'; box.style.left = Math.max(10, Math.min(rect.left - width / 3, innerWidth - width - 10)) + 'px'; box.style.top = Math.max(width + 10, rect.top) + 'px'; }, 300); }); document.addEventListener('pointerout', event => { if (event.target.closest && event.target.closest('#twikoo-wrap .OwO-item img')) hide(); }); document.addEventListener('pjax:send', hide); addEventListener('scroll', hide, true); })();
|
然后在 _config.butterfly.yml 的 inject.bottom 里引入。
1 2 3
| inject: bottom: - <script defer src="/static/js/owo-preview.js"></script>
|
如果和我一样用了 JS 自动注入,就在 source/static/js/_inject.yml 的 bottom 里添加文件名,不要重复引入。这里绑定了全局事件的脚本设为 pjax: false,避免切换页面时重复绑定。
脚本使用 DOM 方法设置图片,避免把地址再次拼成 HTML。以后升级表情组件时,记得看看 .OwO-item img 这个选择器是否还对应表情图片。