点击查看更新记录
更新记录
2021-01-29:正式版v1.0
- 1.0版本正式版发布。最终确定此帖为
SAO UI
右半边菜单,适合可用菜单项较少且习惯精简风格的用户。
2021-01-28:内测版v0.37
- 正式版待发布,因为显隐逻辑完全改变,预备转至新帖;
2021-01-26:内测版v0.21
- 新增了3D模式下浮动效果
- 适配了pjax。调用了Butterfly主题自带的pjax对象。其他主题需要另外适配。
2021-01-25:内测版v0.14
- 新增了3D模式开关配置项
2021-01-24:内测版v0.07
- 实现SAO风格的右键菜单
- 添加了点击音效,默认使用本站同款,可以自定义配置
- 支持添加链接或者自定义脚本动作
- 添加Ctrl+右键转换原生菜单功能
- 几个常用脚本分享
点击查看参考教程
参考方向 | 教程原贴 |
---|---|
菜单边框风格伪类样式实现方案 | 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!
教程正文
SAO UI PLAN 相关项目为本站原创项目,因此均为内测版,在样式适配上仅针对本站进行调整,因此在泛用性上存在缺漏。对于可能遇到的bug,欢迎在评论区进行讨论。
在进行本帖的魔改前,请务必做好备份以便回退。
点击查看教程正文
- 新建
[Blogroot]\themes\butterfly\layout\includes\SAO-menu.pug
: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#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://npm.elemecdn.com/akilar-candyassets/audio/Launcher.wav'
- var Click = theme.SAO_Utils.music.Click ? url_for(theme.SAO_Utils.music.Click) : 'https://npm.elemecdn.com/akilar-candyassets/audio/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
: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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154if 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)
.top
transform rotate3d(1, 1, 0, 35deg)
#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 ;
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 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}
:1
2
3
4
5
6
7if 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
异步加载。因为全部都是触发类函数,在监听到相应的点击或悬停事件之前不会执行,所以甚至不会有加载完成后执行脚本的那段阻塞时间。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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89window.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配置项:
1
2
3
4
5
6
7
8CDN:
# main
main_css: /css/index.css
jquery: https://npm.elemecdn.com/jquery@latest/dist/jquery.min.js
main: /js/main.js
utils: /js/utils.js
+ # SAO_Utils
+ SAO_Utils: /js/SAO_Menu.js - SAO_Utils菜单配置项示例:
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# 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
了
1 | - name: Mirror |
功能:针对gitee镜像站和当前站点的同篇文章跳转,记得更改链接。
1 | function Mirror() { |
功能:打开local-search
搜索按钮(提取自Butterfly源码,其他主题可能不生效)。
1 | function openSearch() { |
功能:打开algolia
搜索按钮(提取自Butterfly源码,其他主题可能不生效)。
1 | function openAlgolia() { |
功能:打开Tidio在线聊天界面(提取自Butterfly源码,其他主题可能不生效)
1 | function openTidio() { |
功能:若当前页面有评论区,则跳转评论区,若没有,则跳转到留言板页面,评论区的挂载ID和留言板路径可能不一致,请自己根据实际情况替换。
1 | function ToComment(){ |
功能:关闭当前页面。既然是SAO
,怎么可以不致敬一下登出键呢?(对无痕窗口不生效,会提示scripts may close only the windows that were opened by them
)
1 | //关闭当前页面 |
TO DO
SAO风格的右键菜单
二级菜单显隐逻辑适配
Ctrl+右键恢复原生菜单
适配pjax,站内跳转不打断全局音乐
补全左侧圆形列表;详见2.0
补全左侧角色属性栏样式;详见2.0
3D显示效果
添加浮动动态动画
Use this card to join the candyhome and participate in a pleasant discussion together .
Welcome to Akilar's candyhome,wish you a nice day .