点击查看更新记录
更新记录
2021-12-19:正式版v2.3
- 新增永久关闭按钮
- 本帖停更
2021-01-31:正式版v2.2
- 新增退出按钮。
- 无痕模式下退出窗口功能会被拦截,变相致敬原著设定。
- 将说明书内容移入默认按钮,可以关闭。
2021-01-30:正式版v2.1
- 修改显隐逻辑,所有次级元素均可以通过点按上级元素来实现常显。
- 优化了左侧属性面板的内容排布,使用绝对高度。
- 属性面板内容超出自动转为滚动条。
- 改动三级菜单的显隐逻辑,也为点按显示
- 新增次级菜单的偏移量调整。
- 新增了说明书配置项,内附本帖链接。希望可以开启帮我做下宣传。
- 调整了3D风格的形变程度。
- 更新了字体,忠于原著。
- 新增悬停显示配置项,可以自己决定是否使用悬停显示
2021-01-29:正式版v2.0
- 继承1.0版本的绝大多数配置项。
- 废弃最大高度,重写样式。全部调整为自动。完全显示菜单。
- 重写显隐逻辑,默认只显示1级菜单。
- 通过点击1级菜单展开2级菜单。
- 通过悬停2级菜单显示3级菜单。
- 优化显示逻辑,识别边缘调整菜单出现位置。确保主要内容完整可见。
- 优化显示逻辑,新增拖动动作监听。可以通过点按拖动菜单调整菜单位置。
- 将1级菜单最后一个按钮默认设置为退出菜单动作。
- 优化退出逻辑。点按空白处也可以退出菜单。
- 2.0版本正式版发布。实现完整UI风格效果。
2021-01-29:正式版v1.0
- 1.0版本正式版发布。
- 确定1.0版本为半边菜单。
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 |
右键菜单显隐逻辑和原生实现方案 | |
UI风格参考,图标、音效资源采集 | SAO Utils |
图标采集 | fontawesome |
pjax适配参考 | |
原生js实现拖拽效果 | js拖拽:简单五步实现元素拖拽功能 |
资源下载
由于本教程涉及的所有修改对缩进格式等有严格要求,担心自己控制不好的可以直接下载静态资源。参照教程进行修改。
写在最前
来自店长的碎碎念
写一个SAO
风格的右键菜单算是我的一个执念了,但是搜遍全网页找不到网页版的内容,于是我想起来多年前就用过的一款软件SAO Utils,可惜它虽然有完整的菜单逻辑,但是却是基于C语言写的。至多只能参考一些音效。
最后兜兜转转,在魔改博客时看到了Volantis
的右键菜单。学习了一下右键菜单的魔改原理。决定自己来从零开始做一个SAO
风格的右键菜单。
因为这个项目,魔怔了大概半个月,好在那半个月单位工作基本划水。(嘛,总之摸鱼也是为了给大家写好看的魔改教程嘛)一直被二级菜单的显隐逻辑所困扰,因为用到了相对定位,中间有一段元素是空白的,没法在不破坏菜单项显示效果的情况下直接依靠hover实现持续显示二级菜单的效果,最后是用定时函数控制绝对显隐,用hover
控制持续显隐。总算是有了一个相对舒适的显隐体验。
在一开始,因为想到以前一直被一些读者喷右键菜单占用了原生菜单很讨厌(说实话这样的读者也很讨厌)。所以这次在@卓越科技建议下添加了ctrl+右键打开原生右键菜单的功能。然后考虑到菜单界面对手机不友好,干脆对手机不生效了。
在添加音效时,因为直接链接跳转的话,会来不及启动点击音效,所以只能使用超时函数设置了0.5
秒的延迟,给音效播放留点时间。
也正是因为不是依赖于a
标签的超链接,而是使用window.location.href
来实现页面重定向,所以目前对于pjax
的适配还是有些许不好。会在切换页面时打断全局音乐。
不过塞翁失马焉知非福,也正是因为如此,我又添加了自定义脚本的配置项接口。可以让读者自己开发各种各样的脚本来丰富菜单功能啦。
说到底,既然可以自定义脚本了,那应该也可以适配pjax实现无伤跳转才对。唉,果然还是太菜了
总之,这次的项目就先告一段落啦!
米娜桑,Link Start!
既然已经制作完成了右半边的SAO
风格UI
,肯定不可能满足于此的啦。
说起来我当初执意要选择软件工程专业的时候,有个动机就是被刀剑神域里的VRMMO吸引,想着能有一天可以真的体验上这种完全沉浸式的游戏。不过理想和现实还真的是差距鲜明啊。
于是开始尝试制作左半侧的UI
。球形风格还是很好实现的。大概只花了半天摸鱼时间,就基本实现了球形样式UI
和悬停变色效果,得益于之前做右半边的经验,不管是构建主体还是引入图标都是顺风顺水。
然后在追番考古时发现左侧菜单还有一个属性面板的界面。OK,话不多说。继续加。
总的来说,在熟练掌握相对定位的关系以后,适配起来还是很容易的。因为右键菜单是动态出现的,故而我用的都是绝对长度,自然也不会去考虑和窗口整体大小比例自适应的问题。
一开始是在静态页面上写的纯静态效果,依靠hover
动作控制显隐属性。但是这就导致我遇到了一系列问题。relative
定位下,100%
这个概念居然是相对于父元素的,依靠各种偏移量搭建的菜单一下子就乱了套。为了调整各个子菜单,重新捡起了初中数学知识,列了一堆二元方程组,最后还真的让我整出了一套计算公式。不过静态的css
是不支持这种玩法的啦,好在到时候实装时还有pug
和stylus
可以添加计算变量动态调整。小case
啦。
然后左半边菜单就搞定啦,悬停显示效果和动画里那是一模一样啊。开心!
然后,好吧,要把左半边菜单和右半边菜单组装起来。淦,一想到之前为了调整右侧菜单的显隐逻辑付出的血泪我就恨不得吐血三升。网上的参考内容都是针对于子菜单在父级元素内部的情况,那确实可以靠hover
轻松搞定,但是我设置了一堆偏移量和伪类,导致子菜单和父菜单关键的连接轴是个伪类,hover
无效啊喂!。在体验了两天的天下武功唯快不破以后设置了三种逻辑。
一种是全部通过点击来展开子菜单。但是这样子一来每次点击都要记得关闭,用清空已激活项来初始化的话,二级和三级又要写另一套逻辑。Pass
。
一种是依靠悬停加延时消失来控制显隐。虽然也算是靠谱了,但是二级消失连带着三级也消失了,还是得天下武功,唯快不破!把延时消失时间设长,观感又很差。Pass
。
最后一种是被读者点醒的,所以说当局者迷旁观者清啊,既然前两种方案都是半吊子,那把它们组合起来。一级菜单用点击来控制二级菜单显隐,二级菜单用悬停延时来控制三级菜单显隐,那不就没问题了嘛!说起来SAO Utils就是这个显隐逻辑。啊,顿悟的感觉真爽,我感觉我现在起码能烧出十颗舍利子。
显隐逻辑处理好以后,再做边缘判断。为了确保完整可见,要充分考虑每次点击的情况,感觉又回到了初中数学课堂,分类讨论,列算式,化简,emmm,我应该还算对得起我初中的数学老师吧。这时候又想起洪哥之前吐槽的,点击位置和菜单出现位置偏移有点大了。
没办法,那就再加个可以拖动的吧。诶?那我还做边缘判断干啥?
总之,磕磕绊绊的,总算是可以把完整的SAO UI
复现到网页中了。
嗯,再来一次。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
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#SAO-back
#SAO-menu
#SAO-menu-content
.utils-list
if theme.SAO_Utils.util_list
each util in theme.SAO_Utils.util_list
.utils-list-item
if util.panel
.user-panel(style=`top:`+ util.panel.top + `;`)
.user-panel-name!= util.panel.title
.user-panel-img
- var userImg = util.panel.img ? url_for(util.panel.img) : 'https://npm.elemecdn.com/akilar-candyassets/image/info.png'
img(src=userImg)
.user-panel-properties!=`${util.panel.prop}`
if util.link
i(class=util.icon onclick=`clickAudio();setTimeout(function(){SAOclose();linkStart('` +url_for(util.link)+ `')},500);`)
else if util.action
i(class=util.icon onclick=`clickAudio();setTimeout(function(){SAOclose();` + util.action + `},500);`)
else
i(class=util.icon onclick="panelAudio();UtilsClick()")
if util.menu_list
- var menuTop = util.menutop ? util.menutop : -((util.menu_list.length)*40/2+43)
.menu-list(style=`top:` + menuTop + `px;`)
each item in util.menu_list
.menu-list-item
if item.link
i(class=item.icon onclick=`clickAudio();setTimeout(function(){SAOclose();linkStart('` +url_for(item.link)+ `')},500);`)= item.name
else if item.action
i(class=item.icon onclick=`clickAudio();setTimeout(function(){SAOclose();` + item.action + `},500);`)= item.name
else
i(class=item.icon onclick='panelAudio();MenusClick()')= item.name
if item.child_list
- var childTop = item.childtop ? item.childtop : -((item.child_list.length)*40/2+20)
.menu-child(style=`top:` + childTop + `px;`)
each child in item.child_list
.menu-list-child
if child.link
i(class=child.icon onclick=`clickAudio();setTimeout(function(){SAOclose();linkStart('` +url_for(child.link)+ `')},500);`)= child.name
else if child.action
i(class=child.icon onclick=`clickAudio();setTimeout(function(){SAOclose();` + child.action + `},500);`)= child.name
else
i(class=child.icon)= child.name
if theme.SAO_Utils.Instructions ? theme.SAO_Utils.Instructions : true
.utils-list-item
.user-panel
.user-panel-name Instructions
.user-panel-img
img(src='/img/siteicon/favicon.png')
.user-panel-properties
h4 欢迎使用SAO_Utils_Web 2.0
p 点按选项可以持续显示下级菜单。您可以通过按住<kbd>ctrl</kbd>+<kbd>右键</kbd>来恢复使用原生右键菜单,更多内容可点击右侧Option按钮访问教程或Help按钮加入糖果屋QQ群。
center ©Akilarの糖果屋
i.fa.fa-cog(onclick="panelAudio();UtilsClick()")
.menu-list(style="top:-103px;")
.menu-list-item
i.fa.fa-tools(onclick="clickAudio();setTimeout(function(){SAOclose();linkStart('https://akilar.top/posts/fd243d7/')},500);") Option
.menu-list-item
i.fa.fa-question-circle(onclick="clickAudio();setTimeout(function(){SAOclose();linkStart('https://jq.qq.com/?_wv=1027&k=a08BZRzs')},500);") Help
.menu-list-item
i.fa.fa-sign-out-alt(onclick="alertAudio();openLogout()") Logout
.utils-list-item
i.fa.fa-power-off(onclick='alertAudio();SAOKeepOff()' title="永久关闭SAO右键菜单")
if theme.SAO_Utils.Instructions ? theme.SAO_Utils.Instructions : true
#SAO-logout
.logout-title Alert
.logout-alert 是否确认退出?
.logout-button
span.logout-confirm
button.far.fa-circle(type="button" name="confirm" onclick="clickAudio();confirmLogout()")
span.logout-cancel
button.fa.fa-times(type="button" name="cancel" onclick="panelAudio();cancelLogout()")
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'
- var Panel = theme.SAO_Utils.music.Panel ? url_for(theme.SAO_Utils.music.Panel) : 'https://npm.elemecdn.com/akilar-candyassets/audio/Panel.wav'
- var Alert = theme.SAO_Utils.music.Alert ? url_for(theme.SAO_Utils.music.Alert) : 'https://npm.elemecdn.com/akilar-candyassets/audio/Alert.wav'
audio#SAOlauncher(src=Launcher)
audio#SAOClick(src=Click)
audio#SAOPanel(src=Panel)
audio#SAOAlert(src=Alert)
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431if hexo-config('SAO_Utils.enable')
@font-face
font-family 'SAOUI'
src url('https://npm.elemecdn.com/akilar-candyassets/fonts/SAOUI.ttf')
font-display swap
#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
#SAO-menu
display block
position absolute
#SAO-menu-content
font-family 'SAOUI' ,Langar,-apple-system, sans-serif
display block
.utils-list-item
font-weight bolder
margin-top 10px
width 66px
height 66px
background rgba(255,255,255,0)
text-align center
vertical-align super
position relative
display block
border-radius 50%
color rgba(255,255,255,0.5)
border 3px solid
box-shadow 2px 2px 2px #888888
i
width 52px
height 52px
background rgba(255,255,255,0.5)
display block
margin auto
vertical-align middle
top 4px
position relative
line-height 100%
border-radius 50%
&::before
color rgba(60, 60, 61,0.7)
font-size 30px
text-align center
font-weight bolder
top 20px
position relative
&:hover
color #eda60c
i
background #eda60c
&::before
color white
.user-panel
transform rotate3d(-1,1,0,20deg)
display none
position absolute
width 250px
height auto
background rgba(231, 231, 232,0.9)
border-radius 10px
left -290px
top -170px
box-shadow 3px 3px 2px #888888
&:before
position absolute
content ''
border-left 25px solid #f9f9f9
border-top 10px solid transparent
border-bottom 10px solid transparent
left 250px
top calc(50% - 10px)
.user-panel
.user-panel-name
font-size 20px
color rgba(60, 60, 61,0.7)
display block
width 100%
height 30px
position relative
background #f9f9f9
border-top-left-radius 10px
border-top-right-radius 10px
.user-panel-img
padding-inline 30px
padding-bottom 0px
padding-top 20px
width 100%
height 50%
overflow hidden
background #f9f9f9
text-align center
box-shadow -1px 10px 15px #888888
img
width 100%
&::after
position absolute
content ''
border-top 15px solid #f9f9f9
border-right 10px solid transparent
border-left 10px solid transparent
left 30%
top calc(50% + 45px)
.user-panel-properties
font-size 18px
padding 20px
margin 0 auto
height 150px
width 100%
overflow scroll
display inline-block
color rgba(60, 60, 61,0.7)
text-align left
&::-webkit-scrollbar
display none
.user-panel-name
&::after
display block
width 80%
margin-left 10%
height 2px
content ''
background rgba(160, 159, 160,0.9)
border-radius 5px
.menu-list
display none
position relative
border 1px solid
left 90px
border-image linear-gradient(to top, transparent, #f9f9f9 20%, #f9f9f9 80%, transparent) 0 0 0 1
margin-left 10px
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%)
.menu-list-item
display block
border-radius 3px
background rgba(249, 249, 249, 0.79)
color #494748
width 180px
padding 20px 25px
margin-bottom 5px
position relative
left -25px
font-weight bolder
color rgb(77, 72, 73)
box-shadow 3px 3px 2px #888888
height 40px
text-align left
&:last-child
margin-bottom 0
&:hover
cursor pointer
background-color #eda60c
color #f9f9f9
.menu-child
display block
z-index 10000
i
&::before
cursor pointer
background-color #f9f9f9
color #eda60c
i
display block
vertical-align super
font-family 'SAOUI' ,Langar,-apple-system, sans-serif
font-size 20px
position absolute
border-radius 0
background rgba(77, 72, 73,0)
left -10px
width 100%
margin-left 15px
top -2px
left -15px
padding-top 10px
&::before
margin-inline 15px
width 20px
height 20px
font-size 16px
color white
background rgb(77, 72, 73)
padding 5px
border-radius 50%
top -2px
@extend .fontawesomeIcon
.menu-child
display none
position relative
border 1px solid
left 180px
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%)
.menu-list-child
display block
border-radius 3px
background rgba(249, 249, 249, 0.79)
color #494748
width 180px
padding 20px 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 40px
&:last-child
margin-bottom 0
i
display block
font-family 'SAOUI' ,Langar,-apple-system, sans-serif
font-size 20px
vertical-align super
position absolute
border-radius 0
background rgba(77, 72, 73,0)
left -10px
width 100%
height 40px
margin-left 15px
top -2px
left -15px
padding-top 10px
&::before
margin-inline 15px
font-size 16px
color white
width 20px
height 20px
background rgb(77, 72, 73)
padding 5px
border-radius 50%
top -2px
@extend .fontawesomeIcon
&:hover
cursor pointer
background-color #eda60c
color #f9f9f9
i
&::before
cursor pointer
background-color #f9f9f9
color #eda60c
.active
display block
if hexo-config('SAO_Utils.ThreeD')
#SAO-menu
animation Updown 1s linear infinite alternate
.utils-list-item
.user-panel
transform rotate3d(0,-1,0,20deg)
.menu-list
transform rotate3d(0,1,0,20deg)
.menu-child
transform rotate3d(0,1,0,5deg)
.user-panel
.user-panel-img
box-shadow -1px 10px 15px #888888
@keyframes Updown
from
margin-top 20px
to
margin-top 10px
if hexo-config('SAO_Utils.hoverShow') ? hexo-config('SAO_Utils.hoverShow') : 1
.utils-list-item
&:hover
.user-panel
display block
z-index 10000
.menu-list-item
&:hover
.menu-child
display block
z-index 10000
if hexo-config('SAO_Utils.Instructions') ? hexo-config('SAO_Utils.Instructions') : 1
#SAO-logout
z-index 9999
background rgba(204, 204, 207,0.8)
font-family 'SAOUI' ,Langar,-apple-system, sans-serif
font-weight bolder
text-shadow 1px 1px 1px #888888
height 240px
width 350px
display none
position fixed
left 0
right 0
top 0
bottom 0
margin auto
border-radius 5px
box-shadow 2px 2px 10px #888888
.logout-title
background rgba(249, 249, 249, 0.8)
color rgba(60, 60, 61,0.7)
height 60px
width 100%
display block
font-size 20px
text-align center
border-top-left-radius 5px
border-top-right-radius 5px
padding-top 10px
.logout-alert
background rgba(220, 220, 220,0.8)
color rgba(60, 60, 61,0.7)
height 90px
width 100%
display block
text-align center
padding-top 30px
box-shadow 0px 0px 15px rgb(188, 188, 188) inset
.logout-button
background rgba(249, 249, 249, 0.8)
height 90px
width 100%
display block
text-align center
border-bottom-left-radius 5px
border-bottom-right-radius 5px
padding-top 30px
.logout-confirm
background rgba(#2f79d4,0)
border-radius 50%
display inline-block
width 36px
height 36px
margin-inline 60px
border 1px solid
border-color #2f79d4
button
background #2f79d4
text-align center
border-radius 50%
font-size 18px
color white
display block
width 30px
height 30px
margin 2px
.logout-cancel
background rgba(#cb3749,0)
border-radius 50%
display inline-block
width 36px
height 36px
margin-inline 60px
border 1px solid
border-color #cb3749
button
background #cb3749
text-align center
border-radius 50%
font-size 18px
font-weight bolder
color white
display block
width 30px
height 30px
margin 2px
.activeLogout
display block
animation flashOpen 1s ease alternate
@keyframes flashOpen
from
transform rotateX(90deg)
to
transform rotateX(0deg) - 修改
[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
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
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305window.document.oncontextmenu = function(event) {
if (event.ctrlKey) return true; //ctrl+右键 使用原生右键
if (/Android|webOS|BlackBerry/i.test(navigator.userAgent)) return true; //媒体选择
if (localStorage.getItem("SAOSwitch")==='turnOff') return true //若持续关闭标记值为turnoff则使用原生菜单
return popMenu(event); //打开右键菜单
};
//监听位移动作,实现拖动效果
var dv = document.getElementById("SAO-menu");
var x = 0;
var y = 0;
var l = 0;
var t = 0;
var isDown = false;
dv.onmousedown = function(e) {
x = e.clientX;
y = e.clientY;
l = dv.offsetLeft;
t = dv.offsetTop;
isDown = true;
dv.style.cursor = "move"
};
window.onmousemove = function(e) {
if (isDown == false) {
return
}
var nx = e.clientX;
var ny = e.clientY;
var nl = nx - (x - l);
var nt = ny - (y - t);
dv.style.left = nl + "px";
dv.style.top = nt + "px"
};
dv.onmouseup = function() {
isDown = false;
dv.style.cursor = "default"
};
//监听鼠标点击动作,点击空白处关闭菜单
document.addEventListener("click", function(event) {
if (event.target.id === 'SAO-back'){
SAOclose()
}
});
//关闭菜单
function SAOclose() {
var mymenu = document.getElementById('SAO-back');
mymenu.style.display = "none";
}
//初始化,隐藏所有active类
function SAOinit() {
var activedItems = document.querySelectorAll('.active');
if (activedItems) {
for (i = 0; i < activedItems.length; i++) {
activedItems[i].classList.remove('active');
}
}
}
//二级菜单初始化,隐藏所有三级菜单的active类
function Menuinit(menu) {
var activedChild = menu.querySelectorAll('.active');
if (activedChild) {
for (i = 0; i < activedChild.length; i++) {
activedChild[i].classList.remove('active');
}
}
}
//link start,处理链接跳转的pjax请求;调用了主题自带的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 panelAudio() {
var panelAudio = document.getElementById("SAOPanel");
if (panelAudio) {
panelAudio.play();//有音频时播放
}
}
//警告音效
function alertAudio() {
var alertAudio = document.getElementById("SAOAlert");
if (alertAudio) {
alertAudio.play();//有音频时播放
}
}
//关闭当前页面
function confirmLogout(){
setTimeout(function(){
window.opener=null;
window.open('','_self');
window.close();
},500);
}
function openLogout(){
SAOclose();
var logoutWindow = document.getElementById('SAO-logout');
logoutWindow.classList.add('activeLogout');
}
function cancelLogout(){
var logoutWindow = document.getElementById('SAO-logout');
logoutWindow.classList.remove('activeLogout');
}
//监听一级菜单点击动作,控制二级菜单显隐
function UtilsClick(){
var thisElement = event.target;
// console.log('click at', thisElement);
var hasMenu = thisElement.parentElement.querySelector('.menu-list');
var hasPanel = thisElement.parentElement.querySelector('.user-panel');
var hasActived = thisElement.parentElement.querySelector('.active');
//当有二级菜单或属性栏被激活时
if (hasActived){
// console.log('hasmenu or haspanel]');
//如果有二级菜单且已经激活,则重新隐藏二级菜单
if (hasMenu && (hasMenu.className.indexOf('active') > -1) ){
hasMenu.classList.remove('active');
}
//如果有属性栏且已经激活,则重新隐藏属性栏
if (hasPanel && (hasPanel.className.indexOf('active') > -1) ){
hasPanel.classList.remove('active');
}
}
else{
SAOinit();//先初始化以清除其他的激活项
//若有二级菜单就显示二级菜单
if (hasMenu) {
hasMenu.classList.add('active');
}
//若有属性栏,就显示属性栏
if (hasPanel) {
hasPanel.classList.add('active');
}
}
}
//监听二级菜单点击动作,控制三级菜单显隐
function MenusClick(){
var thisElement = event.target;
// console.log('click at', thisElement);
//当前按钮指向的三级菜单
var hasChild = thisElement.parentElement.querySelector('.menu-child');
//当前按钮所在的二级菜单
var menuParent = thisElement.parentElement.parentElement;
if (hasChild) {
//若已经激活了,则再点击就隐藏;
if (hasChild.className.indexOf('active') > -1) {
hasChild.classList.remove('active');
}
//若未激活
else{
Menuinit(menuParent);//先初始化以清除其他的激活项
hasChild.classList.add('active');//再给当前子菜单添加激活
}
}
}
function popMenu(event) {
//播放菜单打开音乐
SAOinit();
var audio = document.getElementById("SAOlauncher");
if (audio) {
audio.play();//有音乐时打开
}
document.getElementById('SAO-back').style.display = "block";
var mymenu = document.getElementById('SAO-menu');
var screenWidth = document.documentElement.clientWidth || document.body.clientWidth;
var screenHeight = document.documentElement.clientHeight || document.body.clientHeight;
// 菜单显示
mymenu.style.display = 'block';
//---- start 点击位置旁边生成。不自动判断位置
// mymenu.style.left = (event.clientX + mymenu.clientWidth/2) + "px";
// mymenu.style.top = (event.clientY - mymenu.clientHeight/2) + "px";
//--- end 点击位置旁边生成。不自动判断位置
//---- start 自动判断确保给菜单边缘留下足够位置
//根据当前位置决定菜单出现位置,确保菜单可完整显示
var currentX = event.clientX;
var currentY = event.clientY;
//X轴判断
//偏左
if (currentX < 230){
mymenu.style.left = (mymenu.clientWidth/2 + 230) + "px";
}
//偏右
else if ((screenWidth - currentX) < 320 ){
mymenu.style.left = (mymenu.clientWidth/2 - 320 + screenWidth) + "px";
}
//默认情况
else{
mymenu.style.left = (currentX + mymenu.clientWidth/2) + "px";
}
//Y轴判断
//偏高
if (currentY < (mymenu.clientHeight/2 + 130)){
mymenu.style.top = "130px";
}
//偏低
else if ((screenHeight - currentY) < (80 + mymenu.clientHeight/2)){
mymenu.style.top = ( screenHeight - mymenu.clientHeight - 80) + "px";
}
//默认情况
else {
mymenu.style.top = (event.clientY - mymenu.clientHeight/2) + "px";
}
//---- end 自动判断确保给菜单边缘留下足够位置
//屏蔽原生菜单
return false;
}
//自定义动作
//搜索按钮动作
function openSearch() {
document.body.style.cssText = 'width: 100%;overflow: hidden'
document.querySelector('#local-search .search-dialog').style.display = 'block'
document.querySelector('#local-search-input input').focus()
btf.fadeIn(document.getElementById('search-mask'), 0.5)
if (!loadFlag) {
search(GLOBAL_CONFIG.localSearch.path)
loadFlag = true
}
// shortcut: ESC
document.addEventListener('keydown', function f (event) {
if (event.code === 'Escape') {
closeSearch()
document.removeEventListener('keydown', f)
}
})
}
//在线聊天按钮动作
function openTidio() {
window.tidioChatApi.show();window.tidioChatApi.open();
}
//镜像站跳转动作
function Mirror() {
let pathname;
let hostname;
let url;
pathname = window.location.pathname;
hostname = window.location.hostname;
if (hostname === 'akilar.top') {
url = "https://akilar.gitee.io" + pathname;
window.alert("即将前往糖果屋分店🍬");
window.location.href = url;
}
else if(hostname === 'akilar.gitee.io') {
url = "https://akilar.top" + pathname;
window.alert("正在返回糖果屋本部🍭!");
window.location.href = url;
}
else {
window.alert("Master,本地调试不需要跳转哦!🍫");
}
}
//评论窗口跳转
function ToComment(){
var hasComment = document.getElementById('post-comment');
if (hasComment){
window.location.href = '#post-comment'; //如果有评论区就跳转到评论区
}
else{
linkStart('/comments/');//如果没有,就跳转到留言板
};
}
//评论窗口跳转
function ToComment(){
var hasComment = document.getElementById('post-comment');
if (hasComment){
window.location.href = '#post-comment'; //如果有评论区就跳转到评论区
}
else{
linkStart('/comments/');//如果没有,就跳转到留言板
};
}
//固化关闭右键菜单效果
function SAOKeepOff(){
localStorage.setItem("SAOSwitch", "turnOff");//将关闭状态激活
SAOclose();
}
//控制开关右键菜单效果
function SAOSwitch(){
var SAOSwitch = localStorage.getItem("SAOSwitch");
// 若键值存在且为turnoff,则转为turn;同时打开右键菜单
if (SAOSwitch === "turnOff"){
localStorage.setItem("SAOSwitch", "turnOn");
popMenu(event); // 打开右键菜单
}else { //否则,持续关闭右键菜单
SAOKeepOff()
}
} - 修改
[Blogroot]\themes\butterfly\layout\includes\rightside.pug
,加入恢复按钮1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#rightside-config-show
if is_post()
if (theme.readmode || theme.translate.enable || (theme.darkmode.enable && theme.darkmode.button) || theme.change_font_size)
button#rightside_config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
if showToc
button#mobile-toc-button.close(type="button" title=_p("rightside.toc"))
i.fas.fa-list-ul
else if theme.translate.enable || (theme.darkmode.enable && theme.darkmode.button) || theme.change_font_size
button#rightside_config(type="button" title=_p("rightside.setting"))
i.fas.fa-cog.fa-spin
+ if theme.SAO_Utils.enable
+ button#ranklist_show(type="button" title=_p("rightside.SAOSwitch") onclick="panelAudio();SAOSwitch()")
+ i.fas.fa-mouse
if theme.chat_btn
button#chat_btn(type="button" title=_p("rightside.chat_btn"))
i.fas.fa-sms - 修改
[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
28
29
30
31
32
33
34
35
36
37
38# SAO_Utils右键菜单
SAO_Utils:
# 基础选项
enable: true # 总开关
ThreeD: true # 3D效果
Instructions: true # 说明书
hoverShow: true # 悬停显示属性面板
music:
enable: true # 点击音效开关
Launcher: /assets/Launcher.wav #启动音效,留空则使用默认项
Click: /assets/Click.wav # 点击音效,留空则使用默认项
# 菜单显示
# 一级菜单
util_list:
# 配置图标选项、属性面板和二级菜单
- icon: fa fa-question-circle
panel: # 选填,左侧属性面板
title: Help # 选填,面板标题
img: /img/info.png # 选填,面板图片,留空则使用默认项
prop: Anything can I help you ? #选填,面板内容,支持html语法
top: -170 # 选填,面板偏移量,用来控制和一级菜单按钮的对齐,留空则使用默认项
link: # 链接,和action、menu_list互斥,只能填一个
action: # 自定义动作,和link、menu_list互斥,只能填一个
menutop: #控制二级菜单的偏移量来调整和一级菜单的对齐,一般不用填。
menu_list: # 二级菜单,和action、link互斥
- name: Tidio # 名称
icon: fa fa-comment-dots # 图标
action: openTidio() # 自定义动作
- name: Candyhome
icon: fa fa-user-friends
link: https://jq.qq.com/?_wv=1027&k=a08BZRzs
- name: Document
icon: fa fa-folder-open
childtop: #控制三级菜单的偏移量来调整和二级菜单的对齐,一般不用填。
child_list: # 三级菜单
- name: Beautify # 名称
icon: fa fa-file-invoice # 图标
link: /posts/f99b208/ # 链接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# SAO_Utils右键菜单
SAO_Utils:
enable: true
ThreeD: true
Instructions: true
hoverShow: true
music:
enable: true
Launcher: /assets/Launcher.wav
Click: /assets/Click.wav
Alert: /assets/Alert.wav
Panel: /assets/Panel.wav
util_list:
- icon: fa fa-link
link: /link/
panel:
title: Friends
img:
prop: Welcome to Sword Art Online !
top:
- icon: fa fa-question-circle
panel:
title: Help
img:
prop: Anything can I help you ?
top:
menu_list:
- name: Tidio
icon: fa fa-comment-dots
action: openTidio()
- name: Comments
icon: fa fa-comments
action: ToComment()
- name: Candyhome
icon: fa fa-user-friends
link: https://jq.qq.com/?_wv=1027&k=a08BZRzs
- icon: fa fa-tasks
panel:
title: Menu
img:
prop: The menu of my blog
top:
menu_list:
- name: Home
icon: fa fa-home
link: https://blog.akilar.top/
- name: Document
icon: fa fa-folder-open
child_list:
- name: Beautify
icon: fa fa-file-invoice
link: /posts/f99b208/
- name: Optimize
icon: fa fa-file-invoice
link: /posts/7c16c4bb/
- name: Tag_Plugins
icon: fa fa-file-invoice
link: /posts/615e2dec/
- name: Construct
icon: fa fa-file-invoice
link: /posts/6ef63e2d/
- name: Blog
icon: fa fa-blog
link: /
- name: Archives
icon: fa fa-archive
link: /archives/
- name: Categoties
icon: fa fa-folder-open
link: /categories/
- name: Tags
icon: fa fa-tags
link: /tags/
- icon: fa fa-tools
panel:
title: Options
img:
prop: Some userful scripts
top:
menu_list:
- name: Mirror
icon: fa fa-indent
action: Mirror()
- name: Random
icon: fa fa-random
link: /random/
- name: Search
icon: fa fa-search
action: openSearch()
- CDN配置项:
- 因为这次的配置逻辑较为繁复,所以参数解释会比较多:
序号 | 参数 | 备选值 | 参数释义 |
---|---|---|---|
0 | enable | true , false | true 为开启右键菜单,false 为关闭右键菜单 |
1 | ThreeD | true , false | true 为开启3D效果,false 为关闭3D效果 |
2 | Instructions | true , false | true 为开启说明书,false 为关闭说明书。默认开启。内附本帖链接,可能的话,希望可以开着帮我做下宣传 |
3 | hoverShow | true , false | true 为开启悬停显示,false 为关闭悬停显示。默认开启。控制属性栏和三级菜单的悬停显隐。 |
4.1 | music.enable | true , false | true 为开启点击音效,false 为关闭点击音效 |
4.2 | music.Launcher | Url,音乐文件的相对路径或外链 | 右键点击打开菜单时的音效,留空则使用默认音效 |
4.3 | music.Click | Url,音乐文件的相对路径或外链 | 左键点击菜单选项时的音效,留空则使用默认音效 |
4.4 | music.Alert | Url,音乐文件的相对路径或外链 | 右键点击退出按钮时的音效,留空则使用默认音效 |
4.5 | music.Panel | Url,音乐文件的相对路径或外链 | 左键点击含子菜单的选项时的音效,留空则使用默认音效 |
5 | util_list | 见下文 | 一级菜单选项 |
5.1 | util_list.icon | eg:fa fa-link | 菜单选项图标,使用fontawesome,也可以使用iconfont |
5.2 | util_list.panel | 见下文 | 角色属性面板 |
5.2.1 | util_list.panel.title | Text | 面板标题 |
5.2.2 | util_list.panel.img | Url,图片相对链接或图床绝对链接 | 面板图片,默认使用角色属性面板图 |
5.2.3 | util_list.panel.prop | Html | 面板内容,支持Html语法 |
5.2.4 | util_list.panel.top | Number eg:-150 | 属性面板偏移量,确保面板右侧箭头对着相应的一级菜单,填写数字即可,不需要加单位 |
5.3 | util_list.link | Url | 链接,站内建议使用相对路径,站外需要使用带协议的绝对路径,与action 、menu_list 互斥,只能填写一个 |
5.4 | util_list.action | Function | 点击动作,详见本帖拓展内容,与link 、menu_list 互斥,只能填写一个 |
5.5 | util_list.menutop | Number eg:-150 | 二级菜单面板偏移量,确保面板右侧箭头对着相应的一级菜单,填写数字即可,不需要加单位 |
5.6 | util_list.menu_list | 见下文 | |
5.6.1 | menu_list.name | Text | 菜单选项标题 |
5.6.2 | menu_list.icon | eg:fa fa-link | 菜单选项图标,使用fontawesome,也可以使用iconfont |
5.6.3 | menu_list.link | Url | 链接,站内建议使用相对路径,站外需要使用带协议的绝对路径,与action 、child_list 互斥,只能填写一个 |
5.6.4 | menu_list.action | Function | 点击动作,详见本帖拓展内容,与link 、child_list 互斥,只能填写一个 |
5.6.5 | menu_list.childtop | Number eg:-150 | 二级菜单面板偏移量,确保面板右侧箭头对着相应的一级菜单,填写数字即可,不需要加单位。 |
5.6.6 | menu_list.child_list | 类似于menu_list | menu_list 相同 |
5.6.6.1 | child_list.name | Text | 菜单选项标题 |
5.6.6.2 | child_list.icon | eg:fa fa-link | 菜单选项图标,使用fontawesome,也可以使用iconfont |
5.6.6.3 | child_list.link | Url | 链接,站内建议使用相对路径,站外需要使用带协议的绝对路径,与action 互斥,只能填写一个 |
5.6.6.4 | 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,站内跳转不打断全局音乐
补全左侧圆形列表
补全左侧角色属性栏样式
3D显示效果
添加浮动动态动画
后日谈
SAO右键风格菜单以后,我有收到过赞美,也收到过抱怨。
毕竟,替换了原生右键菜单以后,会有很多不便。比如不能直接复制粘贴代码。不能使用右键原生菜单的诸多功能,连带着把浏览器插件的附加功能也屏蔽了。
所以我加上了ctrl+右键时打开原生菜单,让读者可以自主选择是否启用右键菜单。但是还是有人不满。还是有人会抱怨。
我其实最想要的,就是有个人在我实现这个功能的时候,在我旁边说上一句,这个功能好酷!
但是很少。只有屈指可数的魔改同好或者刀剑神域粉会在访问博客时赞叹一句。所以每当有人夸上一句,我都能乐上好半天。那种感觉比喝了蜜还甜。
我也收到过类似于“多余的功能””根本没人会用”“很搞心态”的评价。
其实他们的评价于我来说无关痛痒,作为个人博客,我甚至完全没有必要去理会。
制作一个SAO风格的右键菜单是我的夙愿,即使它的小众性质注定了它无法被任何人接受。可我也不想我费尽心思完成的作品被无端的谩骂。
今天,我心不甘情不愿的加上了永久固化关闭右键菜单功能。依旧交出了选择的权利。改动很简单。不超过20行代码。对我来说称得上举手之劳。
我已经妥协了太多次了。因为一个众口难调,我不停的改动功能,不停地加上二联判断,不停地添加开关配置项,尽可能得让每一篇教程里的每一种方案都给予使用者充分的选择权利。
我已经厌倦了不断的妥协了。
Use this card to join the candyhome and participate in a pleasant discussion together .
Welcome to Akilar's candyhome,wish you a nice day .