点击查看更新记录

更新记录

2022-03-22:测试版

  1. 基本UI实现
  2. 基本功能实现
点击查看参考教程
参考方向教程原贴
动作监测参考dorakika-导航测试

写在最前

虽然当时构思的时候想法很丰满,但是做出来以后突然觉得功能好鸡肋。左右浮动切换上下篇算是唯一的亮点了。上下按钮用拖动方式体感上还不如直接用侧栏菜单的按钮功能来的方便。尤其是考虑到PC端,手机端,窄屏设备,触屏电脑等设备动作的监测判断。总是牵扯到一大堆的交集。然后就是点击动作在某个屏宽比下会执行两次。
Dorakika的代码我也没吃透,似乎有个长按以后能够拖动悬浮菜单的功能,搞不好我多删了一些代码,长按以后拖动的结束动作一直没法按照期望的来。
这个悬浮按钮不打算实装了,作为学习用吧。bug太多,不修啦。

魔改步骤

SAO UI PLAN 相关项目为本站原创项目,因此均为内测版,在样式适配上仅针对本站进行调整,因此在泛用性上存在缺漏。对于可能遇到的bug,欢迎在评论区进行讨论。

在进行本帖的魔改前,请务必做好备份以便回退。

  1. 新建[Blogroot]\themes\butterfly\source\js\SAO-controlldot.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
    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
    let touchStartTime,
    touchStartPos; //start信息
    let mouseDown = false; //mousemove事件绑定在window上,mouseDown变量判断当前是否为悬浮菜单被按下,再进行move判断
    let isMoveDot = false; //悬浮菜单是否为可移动状态
    let isFirstMove = false; //因为move事件要移动才能触发,start时刷新为true,保证对第一次move的识别
    let startTip; //提示可移动

    function start(e){
    isMoveDot = false;
    isFirstMove = true;
    touchStartTime = e.timeStamp;
    touchStartPos = [e.changedTouches[0].clientX,e.changedTouches[0].clientY];
    startTip = setTimeout(function(){
    // kk.changeMessage("现在可移动了",2000,'green');
    },1000); //长按设置的是1000ms后可移动(不移动move不会触发,这里可以提示一下,进入move后可取消这个定时器)
    return false;
    }

    function move(e){
    let touchTime = e.timeStamp-touchStartTime;
    let currentPos = [e.changedTouches[0].clientX,e.changedTouches[0].clientY];
    let offset = [currentPos[0]-touchStartPos[0],currentPos[1]-touchStartPos[1]];
    clearTimeout(startTip);
    if(touchTime > 1000 && isFirstMove){
    isFirstMove = false;
    isMoveDot = true;
    }else if(isFirstMove && offset[1] == 0 && offset[0] == 0){
    // console.log("not move,just auto emit");
    return false;
    }
    isFirstMove = false;

    //移动
    if(isMoveDot){
    document.getElementById('SAO-ctrldot').style.top = `calc(${currentPos[1]*100/document.querySelector('html').clientHeight}% - 30px)`;
    document.getElementById('SAO-ctrldot').style.left = `calc(${currentPos[0]*100/document.querySelector('html').clientHeight}% - 30px)`;
    return false;
    }

    if(offset[0]**2 + offset[1]**2 > 30**2){
    const l = Math.sqrt(offset[0]**2+offset[1]**2);
    offset[0] = 30*offset[0]/l;
    offset[1] = 30*offset[1]/l;
    }

    document.getElementById('SAO-ctrldot').getElementsByClassName('SAO-ctrldot-dot')[0].style.transform = `translate(${offset[0]}px,${offset[1]}px)`;
    }


    function end(e){
    if(isMoveDot){
    isMoveDot = false;
    return false;
    }
    let touchEndTime = e.timeStamp;
    let touchEndPos = [e.changedTouches[0].clientX,e.changedTouches[0].clientY];

    let offset = [touchEndPos[0]-touchStartPos[0],touchEndPos[1]-touchStartPos[1]];
    if(offset[0]**2 + offset[1]**2 > 30**2){
    const l = Math.sqrt(offset[0]**2+offset[1]**2);
    offset[0] = 30*offset[0]/l;
    offset[1] = 30*offset[1]/l;
    }
    // 点按时间,用于判断长按
    let touchTime = touchEndTime-touchStartTime;
    //判定事件
    if(touchTime < 300 && offset[0] == 0 && offset[1] == 0){
    // 监测到点按事件
    console.log("click");
    if ((/Android|webOS|BlackBerry/i.test(navigator.userAgent)) || (document.body.offsetWidth < 900)) { //媒体选择,为移动设备或者屏幕分辨率横屏小于900px
    //提取自main.js部分控制手机端设备目录显隐的代码片段。高耦合。
    if (window.getComputedStyle(document.getElementById('card-toc')).getPropertyValue('opacity') === '0') window.mobileToc.open()
    else window.mobileToc.close()
    }else{ //媒体选择,屏宽分辨率大于900px
    //提取自main.js部分控制侧栏折叠的代码片段。高耦合。
    const $htmlDom = document.documentElement.classList
    $htmlDom.contains('hide-aside')
    ? saveToLocal.set('aside-status', 'show', 2)
    : saveToLocal.set('aside-status', 'hide', 2)
    $htmlDom.toggle('hide-aside')
    }
    }else if(offset[0] == 0 && offset[1] == 0){
    // 监测到长按
    console.log("longClick");
    SAONotify("message","SAO Controller,正在制作中,敬请期待")

    }else if((offset[0]**2+offset[1]**2) < 400){
    console.log("little=>cancel");
    }else if(offset[0] > 0 && (Math.atan(offset[1]/offset[0])/Math.PI*180 < 45) && (Math.atan(offset[1]/offset[0])/Math.PI*180 > -45)){
    //监测到向右
    // console.log("right")
    var nextPost = document.querySelector(".next-post a")
    if(nextPost){//若存在下一篇
    nextHref = nextPost.getAttribute("href");
    nextTitle = nextPost.getAttribute("title");
    nextMessage = "是否跳转至下一篇:<br>" + nextTitle;
    nextAction = `pjax.loadUrl('`+nextHref+`')`;
    SAONotify("Message",nextMessage,nextAction);
    }else{
    SAONotify("Alert","已经没有下一篇了哦");
    }


    }else if(offset[0] < 0 && (Math.atan(offset[1]/-offset[0])/Math.PI*180 < 45) && (Math.atan(offset[1]/-offset[0])/Math.PI*180 > -45)){
    //监测到向左
    // console.log("left")
    var prevPost = document.querySelector(".prev-post a")
    if(prevPost){//若存在下一篇
    prevHref = prevPost.getAttribute("href");
    prevTitle = prevPost.getAttribute("title");
    prevMessage = "是否跳转至上一篇:<br>" + prevTitle;
    prevAction = `pjax.loadUrl('`+prevHref+`')`;
    SAONotify("Message",prevMessage,prevAction);
    }else{
    SAONotify("Alert","已经没有上一篇了哦");
    }


    }else if(offset[1]<0){
    // 监测到向上
    // console.log("up");
    btf.scrollToDest(0, 500); //返回顶部
    }else if(offset[1]>0){
    // 检测到向下
    // console.log("down");
    // 跳转到评论区
    let h;
    if(document.getElementById("post-comment")){
    h = document.getElementById('post-comment').offsetTop;
    }else if(document.getElementById('footer')){
    h = document.getElementById('footer').offsetTop;
    }
    // console.log(h)
    btf.scrollToDest(h, 500);
    // FixedCommentBtn(); //展开评论区
    }else{
    // 未知事件
    // console.log("???")
    SAONotify("Alert","无效的操作"); //弹窗提示无效操作
    }
    // 按钮复位
    document.getElementById('SAO-ctrldot').getElementsByClassName('SAO-ctrldot-dot')[0].style.transform = `translate(0px,0px)`;

    return false
    }
    // 手机端触摸开始事件监听
    document.getElementById('SAO-ctrldot').getElementsByClassName('SAO-ctrldot-dot')[0].addEventListener("touchstart", start);
    // PC端触摸开始事件监听
    document.getElementById('SAO-ctrldot').getElementsByClassName('SAO-ctrldot-dot')[0].addEventListener('mousedown',function(e){
    // if ((/Android|webOS|BlackBerry/i.test(navigator.userAgent)) || (document.body.offsetWidth < 900)) return true
    mouseDown=true;
    // console.log("mStart")
    e.changedTouches = [{clientX:e.clientX,clientY:e.clientY}]
    start(e)
    return false;
    });
    // 手机端触摸事件监听
    document.getElementById('SAO-ctrldot').getElementsByClassName('SAO-ctrldot-dot')[0].addEventListener('touchmove',move);
    // PC端触摸事件监听
    window.addEventListener('mousemove',function(e){
    // if ((/Android|webOS|BlackBerry/i.test(navigator.userAgent)) || (document.body.offsetWidth < 900)) return true
    if(mouseDown){
    e.changedTouches = [{clientX:e.clientX,clientY:e.clientY}];
    move(e);
    return false;
    }
    });
    //手机端触摸结束事件监听
    document.getElementById('SAO-ctrldot').getElementsByClassName('SAO-ctrldot-dot')[0].addEventListener('touchend',end);
    // PC端触摸结束事件监听
    window.addEventListener('mouseup',function(e){
    if ((/Android|webOS|BlackBerry/i.test(navigator.userAgent)) || (document.body.offsetWidth < 900)) return true
    if(mouseDown){
    mouseDown=false;
    e.changedTouches = [{clientX:e.clientX,clientY:e.clientY}]
    end(e,this)
    return false;
    }
    });
    //手机端触摸取消事件监听(用于复位)
    document.getElementById('SAO-ctrldot').addEventListener('touchcancel',function(e){
    console.log("cancel",e)
    document.getElementById('SAO-ctrldot').getElementsByClassName('SAO-ctrldot-dot')[0].style.transform = `translate(0px,0px)`;
    })
  2. 新建[Blogroot]\themes\butterfly\layout\includes\custom\SAO-controlldot.pug

    1
    2
    3
    4
    //- 悬停菜单主体
    #SAO-ctrldot
    .SAO-ctrldot-bg
    .SAO-ctrldot-dot
  3. 新建[Blogroot]\themes\butterfly\source\css\_layout\SAO-controlldot.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
    @media screen and (max-width: 900px)
    #SAO-ctrldot
    right calc(50% - 30px) !important
    top unset !important
    bottom 40px
    #SAO-ctrldot
    display flex
    position fixed
    right 60px
    top calc( 50% - 30px )
    width 60px
    height 60px
    justify-content center
    align-items center
    z-index 1000
    .SAO-ctrldot-bg
    position absolute
    background-color rgba(0, 0, 0, 0.6)
    width 60px
    height 60px
    border-radius 50%
    .SAO-ctrldot-dot
    position absolute
    width 40px
    height 40px
    border-radius 50%
    background-color rgba(255, 255, 255, 0.6)
    transition 0.1s
  4. 修改[Blogroot]\themes\butterfly\layout\includes\layout.pug,在最后一行加入:
    1
    2
    3
    4
    5
    6
    7
    8
              footer#footer(style=footer_bg)
    !=partial('includes/footer', {}, {cache:theme.fragment_cache})
    else
    include ./404.pug
    include ./rightside.pug
    !=partial('includes/third-party/search/index', {}, {cache:theme.fragment_cache})
    include ./additional-js.pug
    + include ./custom/SAO-controller.pug
  5. [Blogroot]\_config.butterfly.ymlinject配置项引入js:
    1
    2
    3
    4
    5
    6
    inject:
    head:
    #SAO-Notify弹窗的依赖。
    - <script src="https://npm.elemecdn.com/akilar-candyassets/image/js/SAO-Notify.js" async></script>
    bottom:
    - <script src="/js/SAO-controller.js" defer></script>

改进方向

可以考虑仅作为手机端的功能,直接用@media让它在PC端隐藏,js里也用媒体选择给它屏蔽掉。这样子的话能避免屏宽比和设备的影响。
还有就是手机端按钮存在遮挡正文的问题,貌似Dorakika是有设计可以拖动位置的,但是代码大概给我误删了。
总的来说,这个悬浮按钮功能会给人眼前一亮的感受,但是因为上下左右点按长按总共不过六个动作,其实能够装载的功能也就那么多。单纯六个动作的话,侧栏按钮就能处理好。