点击查看更新记录
更新记录
- 实现SAO风格的右键菜单
- 添加了点击音效,默认使用本站同款,可以自定义配置
- 支持添加链接或者自定义脚本动作
- 添加Ctrl+右键转换原生菜单功能
- 几个常用脚本分享
- 新增了3D模式开关配置项
- 新增了3D模式下浮动效果
- 适配了pjax。调用了Butterfly主题自带的pjax对象。其他主题需要另外适配。
- 正式版待发布,因为显隐逻辑完全改变,预备转至新帖;
- 1.0版本正式版发布。最终确定此帖为
SAO UI
右半边菜单,适合可用菜单项较少且习惯精简风格的用户。
点击查看参考教程
参考方向 | 教程原贴 |
---|---|
菜单边框风格伪类样式实现方案 | codepen-Pure CSS SAO Menu Thing |
右键菜单显隐逻辑和原生实现方案 | ![]() |
样式风格参考,图标、音效资源采集 | SAO Utils |
图标采集 | fontawesome |
pjax适配参考 | ![]() |
资源下载
由于本教程涉及的所有修改对缩进格式等有严格要求,担心自己控制不好的可以直接下载静态资源。参照教程进行修改。
写在最前
来自店长的碎碎念
2021.01.24
写一个SAO
风格的右键菜单算是我的一个执念了,但是搜遍全网页找不到网页版的内容,于是我想起来多年前就用过的一款软件SAO Utils,可惜它虽然有完整的菜单逻辑,但是却是基于C语言写的。至多只能参考一些音效。
最后兜兜转转,在魔改博客时看到了Volantis
的右键菜单。学习了一下右键菜单的魔改原理。决定自己来从零开始做一个SAO
风格的右键菜单。
因为这个项目,魔怔了大概半个月,好在那半个月单位工作基本划水。(嘛,总之摸鱼也是为了给大家写好看的魔改教程嘛)一直被二级菜单的显隐逻辑所困扰,因为用到了相对定位,中间有一段元素是空白的,没法在不破坏菜单项显示效果的情况下直接依靠hover实现持续显示二级菜单的效果,最后是用定时函数控制绝对显隐,用hover
控制持续显隐。总算是有了一个相对舒适的显隐体验。
在一开始,因为想到以前一直被一些读者喷右键菜单占用了原生菜单很讨厌(说实话这样的读者也很讨厌)。所以这次在@卓越科技建议下添加了ctrl+右键打开原生右键菜单的功能。然后考虑到菜单界面对手机不友好,干脆对手机不生效了。
在添加音效时,因为直接链接跳转的话,会来不及启动点击音效,所以只能使用超时函数设置了0.5
秒的延迟,给音效播放留点时间。
也正是因为不是依赖于a
标签的超链接,而是使用window.location.href
来实现页面重定向,所以目前对于pjax
的适配还是有些许不好。会在切换页面时打断全局音乐。
不过塞翁失马焉知非福,也正是因为如此,我又添加了自定义脚本的配置项接口。可以让读者自己开发各种各样的脚本来丰富菜单功能啦。
说到底,既然可以自定义脚本了,那应该也可以适配pjax实现无伤跳转才对。唉,果然还是太菜了
总之,这次的项目就先告一段落啦!
米娜桑,Link Start!
教程正文
点击查看教程正文
- 新建
[Blogroot]\themes\butterfly\layout\includes\SAO-menu.pug
:#SAO-back
#SAO-menu
#SAO-menu-content
#menu-list
if theme.SAO_Utils.menu_list
each item in theme.SAO_Utils.menu_list
.menu-list-item(onclick='clickAudio()' onmouseover='Mouseover()' onmouseout='Mouseout()')
if item.link
i(class=item.icon onclick=`setTimeout(function(){linkStart('` +url_for(item.link)+ `')},500);`)= item.name
else if item.action
i(class=item.icon onclick=`setTimeout(function(){` + item.action + `},500);`)= item.name
else
i(class=item.icon)= item.name
if item.child_list
.menu-child(style=`top: -` + (30 * (item.child_list.length + 1) ) + `px;`)
each child in item.child_list
.menu-list-child(onclick='clickAudio()')
if child.link
i(class=child.icon onclick=`setTimeout(function(){linkStart('` +url_for(child.link)+ `')},500);`)= child.name
else if child.action
i(class=child.icon onclick=`setTimeout(function(){` + child.action + `},500);`)= child.name
else
i(class=child.icon)= child.name
if theme.SAO_Utils.music.enable
- var Launcher = theme.SAO_Utils.music.Launcher ? url_for(theme.SAO_Utils.music.Launcher) : 'https://cdn.jsdelivr.net/gh/Akilarlxh/Akilarlxh.github.io@bf_3.5.1_6/assets/Launcher.wav'
- var Click = theme.SAO_Utils.music.Click ? url_for(theme.SAO_Utils.music.Click) : 'https://cdn.jsdelivr.net/gh/Akilarlxh/Akilarlxh.github.io@bf_3.5.1_6/assets/Click.wav'
audio#SAOlauncher(src=Launcher)
audio#SAOClick(src=Click)
script(async src=url_for(theme.CDN.SAO_Utils)) - 新建
[Blogroot]\themes\butterfly\source\css\_layout\SAO_Menu.styl
:if hexo-config('SAO_Utils.enable')
#SAO-back
display none
position fixed
width 100%
top 0
left 0
height 100%
background rgba(3, 3, 3, 0.5)
z-index 9999
vertical-align super
if hexo-config('SAO_Utils.ThreeD')
.left
transform rotate3d(-1, -1, 0, 35deg)!important
.top
transform rotate3d(1, 1, 0, 35deg)!important
#SAO-menu
display none
position absolute
border 1px solid
border-image linear-gradient(to top, transparent, #f9f9f9 20%, #f9f9f9 80%, transparent) 0 0 0 1
margin-left 30px
width auto
font-family Langar,-apple-system, sans-serif
if hexo-config('SAO_Utils.ThreeD')
transform rotate3d(-1, 1, 0, 35deg)
animation Updown 1s linear infinite alternate
@keyframes Updown
from
margin-top 20px
to
margin-top 10px
&:before
position absolute
content ''
border-right 25px solid #f9f9f9
border-top 10px solid transparent
border-bottom 10px solid transparent
left -30px
top calc(50% - 10px)
&:after
position absolute
content ''
border 3px solid #333
border-radius 50%
left -20px
top 50%
transform translateY(-50%)
#SAO-menu-content
max-height 300px
overflow scroll
width 430px
padding-top 165px
padding-bottom 15px
&::-webkit-scrollbar
display none
#menu-list
list-style-type none
margin 0 5px
padding 0
width auto
.menu-list-item
background-color rgba(249, 249, 249, 0.79)
width 200px
padding 15px 25px
margin-bottom 5px
font-weight bolder
color rgb(77, 72, 73)
box-shadow 3px 3px 2px #888888
height 50px
border-radius 5px
.active
display: inline-block;
i
vertical-align super
&::before
margin-right 15px
color: white;
background: rgb(77, 72, 73);
padding: 5px;
border-radius: 50%;
&:hover
cursor pointer
background-color #eda60c
color #f9f9f9
i
&::before
color: #eda60c;
background: white;
.menu-child
display inline-block
&:last-child
margin-bottom 0
.menu-list-child
display display
background-color rgba(249, 249, 249, 0.79)
color #494748
width 200px
padding 15px 25px
margin-bottom 5px
position relative
left -25px
top 0px
font-weight bolder
color rgb(77, 72, 73)
box-shadow 3px 3px 2px #888888
height 50px
border-radius 5px
i
vertical-align super
&::before
margin-right 15px
color: white!important;
background: rgb(77, 72, 73)!important;
padding: 5px;
border-radius: 50%;
&:hover
cursor pointer
background-color #eda60c
color #f9f9f9
i
&::before
color: #eda60c!important;
background: white!important;
.menu-child
display none
position relative
border 1px solid
left 170px
border-image linear-gradient(to top, transparent, #f9f9f9 20%, #f9f9f9 80%, transparent) 0 0 0 1
margin-left 20px
width auto
height auto
padding 10px 30px
&:before
position absolute
content ''
border-right 25px solid #f9f9f9
border-top 10px solid transparent
border-bottom 10px solid transparent
left -30px
top calc(50% - 10px)
&:after
position absolute
content ''
border 3px solid #333
border-radius 50%
left -20px
top 50%
transform translateY(-50%) - 修改
[Blogroot]\themes\butterfly\layout\includes\additional-js.pug
,引入右键菜单网页元素,注意butterfly_v3.6.0
取消了缓存配置,转为完全默认,需要将{cache:theme.fragment_cache}
改为{cache: true}
:if theme.pjax.enable
!=partial('includes/third-party/pjax', {}, {cache:theme.fragment_cache})
!=partial('includes/third-party/baidu_push', {}, {cache:theme.fragment_cache})
+ if theme.SAO_Utils.enable
+ !=partial('includes/SAO-menu', {}, {cache:theme.fragment_cache}) - 新建
[Blogroot]\themes\butterfly\source\js\SAO_Menu.js
,控制右键菜单的显隐。此处的脚本引入使用了async
异步加载。因为全部都是触发类函数,在监听到相应的点击或悬停事件之前不会执行,所以甚至不会有加载完成后执行脚本的那段阻塞时间。window.document.oncontextmenu = function(event) {
if (event.ctrlKey) return true; //ctrl+右键 使用原生右键
if (/Android|webOS|BlackBerry/i.test(navigator.userAgent)) return true; //媒体选择
return popMenu(event); //打开右键菜单
};
document.addEventListener("click", function(event) {
var mymenu = document.getElementById('SAO-back');
mymenu.style.display = "none";
});
//处理链接跳转的请求;调用了主题自带的pjax对象。其他主题需要另外适配。
function linkStart(link){
if (link.includes('https://') || link.includes('http://') ){
window.location.href = link;
}
else{
if (pjax){
pjax.loadUrl(link);
}
else{
window.location.href = link;
}
}
}
//点击菜单内元素播放点击音频
function clickAudio() {
var clickAudio = document.getElementById("SAOClick");
if (clickAudio) {
clickAudio.play();//有音频时播放
}
}
//定义二级菜单显隐,监听鼠标悬停动作
function Mouseover() {
var thisChild = event.target.querySelector('.menu-child');
if (thisChild) {
thisChild.classList.add('active');
}
}
function Mouseout() {
var thisChild = event.target.querySelector('.menu-child');
if (thisChild && thisChild.className.indexOf('active') > -1) {
setTimeout(function() {
thisChild.classList.remove('active');
}, 100);
}
}
function popMenu(event) {
//播放菜单打开音乐
var audio = document.getElementById("SAOlauncher");
if (audio) {
audio.play();//有音乐时打开
}
document.getElementById('SAO-back').style.display = "block";
var mymenu = document.getElementById('SAO-menu');
var menuContent = document.getElementById('SAO-menu-content');
var screenWidth = document.documentElement.clientWidth || document.body.clientWidth;
var screenHeight = document.documentElement.clientHeight || document.body.clientHeight;
// 菜单显示
mymenu.style.display = 'block';
menuContent.scrollTop = '150';
//根据当前位置决定菜单出现位置,确保菜单可完整显示
if (event.clientX * 2 > screenWidth) {
if ((event.clientX - menuContent.clientWidth) * 2 > screenWidth) {
mymenu.style.left = (event.clientX - menuContent.clientWidth) + "px";//偏右时左移
}
else {
mymenu.style.left = event.clientX + "px";
}
mymenu.classList.add('left');
} else {
mymenu.style.left = event.clientX + "px";
mymenu.classList.remove('left');
}
if (event.clientY * 2 > screenHeight) {
mymenu.style.top = (event.clientY - menuContent.clientHeight) + "px";//偏高时下降
mymenu.classList.add('top');
} else {
mymenu.style.top = event.clientY + "px";
mymenu.classList.remove('top');
}
if ((event.clientY * 2 > screenHeight) && (event.clientX * 2 > screenWidth)) {
if (mymenu.className.indexOf('top') > -1) {
mymenu.classList.remove('top');
}
if (mymenu.className.indexOf('left') > -1) {
mymenu.classList.remove('left');
}
}
return false; //屏蔽原生菜单
} - 修改
[Blogroot]\_config.butterfly.yml
,添加CDN配置项和菜单选项:- CDN配置项:
CDN:
# main
main_css: /css/index.css
jquery: https://cdn.jsdelivr.net/npm/jquery@latest/dist/jquery.min.js
main: /js/main.js
utils: /js/utils.js
+ # SAO_Utils
+ SAO_Utils: /js/SAO_Menu.js - SAO_Utils菜单配置项示例:
# SAO_Utils右键菜单
SAO_Utils:
enable: true
ThreeD: true
music:
enable: true
Launcher:
Click:
menu_list:
- name: Link Start
icon: fa fa-link
link: /link/
action:
child_list:
- name: Message
icon: fa fa-envelope
link:
action:
child_list:
- name: Tidio
icon: fa fa-server
link:
action: openTidio()
- name: Comments
icon: fa fa-comments
link: /comments/
action:
- CDN配置项:
- 因为这次的配置逻辑较为繁复,所以参数解释会比较多:
参数 | 备选值 | 参数释义 |
---|---|---|
enable | true , false | true 为开启右键菜单,false 为关闭右键菜单 |
ThreeD | true , false | true 为开启3D效果,false 为关闭3D效果 |
music.enable | true , false | true 为开启点击音效,false 为关闭点击音效 |
music.Launcher | 音乐文件的相对路径或外链 | 右键点击打开菜单时的音效,留空则使用默认音效 |
music.Click | 音乐文件的相对路径或外链 | 左键点击菜单选项时的音效,留空则使用默认音效 |
menu_list | 见下文 | 菜单选项 |
menu_list.name | text | 菜单选项标题 |
menu_list.icon | eg:fa fa-link | 菜单选项图标,使用fontawesome,也可以使用iconfont |
menu_list.link | url | 链接,站内建议使用相对路径,站外需要使用带协议的绝对路径,与action 互斥,只能填写一个 |
menu_list.action | function | 点击动作,详见本帖拓展内容,与link 互斥,只能填写一个 |
menu_list.child_list | 类似于menu_list | 仅一级菜单支持该配置项,其余下辖配置项与menu_list相同 |
child_list.name | text | 菜单选项标题 |
child_list.icon | eg:fa fa-link | 菜单选项图标,使用fontawesome,也可以使用iconfont |
child_list.link | url | 链接,站内建议使用相对路径,站外需要使用带协议的绝对路径,与action 互斥,只能填写一个 |
child_list.action | function | 点击动作,详见本帖拓展内容,与link 互斥,只能填写一个 |
自定义脚本拓展
点击查看脚本拓展内容
糖果屋出品的右键菜单提供了自定义js配置,读者可以通过封装自己的js脚本,直接通过菜单选项调用。以下会分享几个简单示例。更多内容可以自行探索。希望可以启发读者,在评论区留下更多有趣的脚本。
使用方法:在上文的menu_list
或者child_list
配置项的action
填写函数名即可正常调用。注意action
和link
互斥。所以写了action
就不要写link
了
- name: Mirror |
功能:针对gitee镜像站和当前站点的同篇文章跳转,记得更改链接。
function Mirror() { |
功能:打开local-search
搜索按钮(提取自Butterfly源码,其他主题可能不生效)。
function openSearch() { |
功能:打开algolia
搜索按钮(提取自Butterfly源码,其他主题可能不生效)。
function openAlgolia() { |
功能:打开Tidio在线聊天界面(提取自Butterfly源码,其他主题可能不生效)
function openTidio() { |
功能:若当前页面有评论区,则跳转评论区,若没有,则跳转到留言板页面,评论区的挂载ID和留言板路径可能不一致,请自己根据实际情况替换。
function ToComment(){ |
功能:关闭当前页面。既然是SAO
,怎么可以不致敬一下登出键呢?(对无痕窗口不生效,会提示scripts may close only the windows that were opened by them
)
//关闭当前页面 |
TO DO
SAO风格的右键菜单
二级菜单显隐逻辑适配
Ctrl+右键恢复原生菜单
适配pjax,站内跳转不打断全局音乐
补全左侧圆形列表;详见2.0
补全左侧角色属性栏样式;详见2.0
3D显示效果
添加浮动动态动画