开发记录

升级记录

2020-10-19:初版搭建

  1. 使用的paddylin教程中的ejs模板
  2. 修改了图标
  3. 添加了镜像站跳转动作和随机文章功能。

2020-10-30:第二版搭建

  1. 将ejs模板转为pug模板。
  2. 修改了引入方式,直接加pug注入而不是通过inject添加js。
  3. 新增配置项,直接修改配置文件即可自定义链接。

2020-10-31:新增bug归纳

  1. Gulp加密压缩后会出现bug。
  2. 添加屏蔽项以避开bug。

2020-11-15:修改css样式

  1. 因与Volantis标签样式冲突。
  2. 修改css选择器,只对右键菜单内元素生效。

右键菜单

参考内容:

  1. 右键菜单源码:PaddyLin-Burrerfly添加鼠标右键功能
    改动范围:使用pug重构代码。配置文件添加自定义点击音乐配置项和自定义链接内容。
  2. 随机文章功能参考:HCLonely-Hexo博客美化
  3. 自己写了个镜像站的跳转动作。能够更好的利用Gitee工具人。

由于本教程涉及的所有修改对缩进格式等有严格要求,担心自己控制不好的可以直接下载静态资源,将压缩包内的butterfly文件夹复制到[Blogroot]\theme\目录下覆盖现有主题文件夹即可跳过以下教程的前6步,直接到主题配置文件_config.butterfly.yml中参照第7步修改配置项。

具体步骤

  1. [Blogroot]\themes\butterfly\layout\includes\third-party\目录下新建galmenu.pug文件。注意缩进。
    link(rel='stylesheet' href=url_for(theme.GalMenu.css))
    .GalMenu.GalDropDown
    #gal.circle
    .ring
    a.menuItem(href=url_for(theme.GalMenu.MenuItem.link1))
    span=theme.GalMenu.MenuItem.item1
    a.menuItem(href=url_for(theme.GalMenu.MenuItem.link2))
    span=theme.GalMenu.MenuItem.item2
    a.menuItem(href=url_for(theme.GalMenu.MenuItem.link3))
    span=theme.GalMenu.MenuItem.item3
    a.menuItem(href=url_for(theme.GalMenu.MenuItem.link4))
    span=theme.GalMenu.MenuItem.item4
    a.menuItem(href=url_for(theme.GalMenu.MenuItem.link5))
    span=theme.GalMenu.MenuItem.item5
    a.menuItem(href=url_for(theme.GalMenu.MenuItem.link6))
    span=theme.GalMenu.MenuItem.item6
    if theme.GalMenu.audio
    audio#audio(src=url_for(theme.GalMenu.music))
    script(src=url_for(theme.GalMenu.js))
  2. 修改[Blogroot]\themes\butterfly\layout\includes\additional-js.pug文件,在末尾添加内容(注意缩进):

          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.GalMenu.enable
    + !=partial('includes/third-party/galmenu', {}, {cache:theme.fragment_cache})
  3. [Blogroot]\themes\butterfly\source\js\目录下新建galmenu.js文件

    var items = document.querySelectorAll('.menuItem');
    for (var i = 0, l = items.length; i < l; i++) {
    items[i].style.left = (50 - 35 * Math.cos(-0.5 * Math.PI - 2 * (1 / l) * i * Math.PI)).toFixed(4) + "%";
    items[i].style.top = (50 + 35 * Math.sin(-0.5 * Math.PI - 2 * (1 / l) * i * Math.PI)).toFixed(4) + "%"
    }


    (function($) {
    var GalMenu = {
    defaults: {
    click_to_close: true,
    stay_open: false
    },
    getCoords: function(e) {
    var evt = e ? e : window.event;
    var clickX = 0,
    clickY = 0;
    if ((evt.clientX || evt.clientY) && document.body && document.body.scrollLeft != null) {
    clickX = evt.clientX + document.body.scrollLeft;
    clickY = evt.clientY + document.body.scrollTop
    };
    if ((evt.clientX || evt.clientY) && document.compatMode == 'CSS1Compat' && document.documentElement && document.documentElement.scrollLeft != null) {
    clickX = evt.clientX + document.documentElement.scrollLeft;
    clickY = evt.clientY + document.documentElement.scrollTop
    };
    if (evt.pageX || evt.pageY) {
    clickX = evt.pageX;
    clickY = evt.pageY
    };
    return Coords = {
    clickX: clickX,
    clickY: clickY,
    clientX: evt.clientX,
    clientY: evt.clientY,
    screenX: evt.screenX,
    screenY: evt.screenY
    }
    },
    init: function(options) {
    var o = options,
    $this = $(this);
    $this.each(function(i) {
    var $this = $(this),
    settings = $.extend({},
    GalMenu.defaults, o),
    $menu = $('.' + settings.menu);
    $this.on('mousedown',
    function(e) {
    if (e.which !== 3 && $(e.target).parents('.GalMenu').length < 1 && settings.click_to_close) {
    $this.find('.GalMenu').stop(true, false).animate({
    opacity: 0
    }, {
    duration: 100,
    queue: false,
    complete: function() {
    $(this).css('display', 'none').find('.active').removeClass('active').next().stop(true, true).slideUp('normal')
    }
    });
    $(".circle").removeClass("open");
    $("#overlay").hide();
    $(".GalMenu").delay(400).hide(0);
    audio.pause();
    audio.currentTime = 0
    }
    });
    $this.on('contextmenu',
    function(e) {
    e.preventDefault();
    e.stopPropagation();
    GalMenu.getCoords(e);
    $('.GalMenu_close_me').stop(true, false).animate({
    opacity: 0
    }, {
    duration: 100,
    queue: false,
    complete: function() {
    $(this).css('display', 'none')
    }
    });
    var audio = $("#audio")[0];
    var add = 150;
    var top = Coords.clientY - add,
    left = ($('body')[0] === e.target) ? Coords.clickX - add : Coords.clientX - add;
    //防止菜单超出可见区域,不需要可注释掉
    var bodyHe = document.documentElement.clientHeight;
    var bodyWi = document.documentElement.clientWidth;
    if (top < 0) top = 0;
    if (bodyHe - Coords.clientY < 150) top = bodyHe - 300;
    if (left < 0) left = 0;
    if ($('body')[0] === e.target) {
    if (bodyWi - Coords.clickX < 150) left = bodyWi - 300
    } else {
    if (bodyWi - Coords.clientX < 150) left = bodyWi - 300
    };
    //防止菜单超出可见区域,不需要可注释掉
    $menu.css({
    top: top + 'px',
    left: left + 'px',
    display: 'block'
    }).stop(true, false).animate({
    opacity: 1
    }, {
    duration: 100,
    queue: false
    });
    if ($("#gal").hasClass("open")) {
    $(".circle").removeClass("open");
    $("#overlay").hide();
    $(".GalMenu").delay(400).hide(0);
    audio.pause();
    audio.currentTime = 0
    } else {
    $(".circle").addClass("open");
    $("#overlay,.GalMenu").show();
    audio.play()
    }
    })
    })
    }
    };
    $.fn.GalMenu = function(method, options) {
    if (GalMenu[method]) {
    return GalMenu[method].apply(this, Array.prototype.slice.call(arguments, 1))
    } else if (typeof method === 'object' || !method) {
    return GalMenu.init.apply(this, arguments)
    } else {
    $.error('Method ' + method + ' does not exist')
    }
    }
    })(jQuery);
    String.prototype.removeWS = function() {
    return this.toString().replace(/\s/g, '')
    };
    String.prototype.pF = function() {
    return parseFloat(this)
    };
    Number.prototype.pF = function() {
    return parseFloat(this)
    };

    $(document).ready(function() {
    $('body').GalMenu({
    'menu': 'GalDropDown'
    })
    });
  4. [Blogroot]\themes\butterfly\source\css\目录下新建galmenu.css文件。如果想要更换右键菜单的图标,可以自定义修改css文件中的图片路径。图标对应从12点方向依次顺时针排列。

    .GalMenu a {
    text-decoration:none;
    outline:0;
    }
    .GalMenu {
    margin:0;
    padding:0;
    display:none;
    position:fixed;
    z-index:999;
    }
    .GalMenu .circle,.ring {
    height:300px;
    position:relative;
    width:300px;
    }
    .GalMenu .circle {
    margin:0 auto;
    }
    .GalMenu .ring {
    border-radius:50%;
    opacity:0;
    -webkit-transform-origin:50% 50%;
    -moz-transform-origin:50% 50%;
    transform-origin:50% 50%;
    -webkit-transform:scale(0.1) rotate(-270deg);
    -moz-transform:scale(0.1) rotate(-270deg);
    -transform:scale(0.1) rotate(-270deg);
    -webkit-transition:all 0.4s ease-out;
    -moz-transition:all 0.4s ease-out;
    transition:all 0.4s ease-out;
    }
    .GalMenu .open .ring {
    opacity:1;
    -webkit-transform:scale(1) rotate(0);
    -moz-transform:scale(1) rotate(0);
    -transform:scale(1) rotate(0);
    }
    .GalMenu .center {
    background-color:rgba(255,255,255,0.3);
    border-radius:50%;
    border:2px solid #ffffff;
    bottom:0;
    color:white;
    height:80px;
    left:0;
    line-height:80px;
    margin:auto;
    position:absolute;
    right:0;
    text-align:center;
    top:0;
    width:80px;
    -webkit-transition:all 0.4s ease-out;
    -moz-transition:all 0.4s ease-out;
    transition:all 0.4s ease-out;
    }
    .GalMenu .open .center {
    border-color:#aaaaaa;
    }
    .GalMenu .menuItem {
    border-radius:50%;
    color:#eeeeee;
    display:block;
    height:80px;
    line-height:80px;
    margin-left:-41px;
    margin-top:-41px;
    position:absolute;
    text-align:center;
    width:80px;
    background-size:80px;
    border:2px #b59494 solid;
    box-shadow:0px 0px 15px #fff;
    -webkit-box-shadow:0px 0px 15px #fff;
    -moz-box-shadow:0px 0px 15px #fff;
    }
    .GalMenu .menuItem:hover {
    box-shadow:inset 0px 0px 80px #fff;
    -webkit-box-shadow:inset 0px 0px 80px #fff;
    -moz-box-shadow:inset 0px 0px 80px #fff;
    }
    /* 图标对应从12点方向依次顺时针排列 */
    .GalMenu .ring a:nth-of-type(1) {
    background-image:url("/img/home.png");
    }
    .GalMenu .ring a:nth-of-type(2) {
    background-image:url("/img/archive.png");
    }
    .GalMenu .ring a:nth-of-type(3) {
    background-image:url("/img/tag.png");
    }
    .GalMenu .ring a:nth-of-type(4) {
    background-image:url("/img/random.png");
    }
    .GalMenu .ring a:nth-of-type(5) {
    background-image:url("/img/link.png");
    }
    .GalMenu .ring a:nth-of-type(6) {
    background-image:url("/img/comment.png");
    }
    .GalMenu .ring a {
    display:inline-block;
    color:#ffffff;
    text-shadow:#DC965A 1px 0 0,#DC965A 0 1px 0,#DC965A -1px 0 0,#DC965A 0 -1px 0;
    -webkit-text-shadow:#DC965A 1px 0 0,#DC965A 0 1px 0,#DC965A -1px 0 0,#DC965A 0 -1px 0;
    -moz-text-shadow:#DC965A 1px 0 0,#DC965A 0 1px 0,#DC965A -1px 0 0,#DC965A 0 -1px 0;
    *filter:Glow(color=#DC965A,strength=1);
    }
    .GalMenu .ring a:hover {
    text-shadow:#66CCFF 1px 0 0,#66CCFF 0 1px 0,#66CCFF -1px 0 0,#66CCFF 0 -1px 0;
    -webkit-text-shadow:#66CCFF 1px 0 0,#66CCFF 0 1px 0,#66CCFF -1px 0 0,#66CCFF 0 -1px 0;
    -moz-text-shadow:#66CCFF 1px 0 0,#66CCFF 0 1px 0,#66CCFF -1px 0 0,#66CCFF 0 -1px 0;
    *filter:Glow(color=#66CCFF,strength=1);
    }
  5. 将下载的资源包中的img文件夹内的图片文件放到[Blogroot]\themes\butterfly\source\img\目录下。

  6. 将下载的资源包中的music文件夹内的音频文件放到[Blogroot]\themes\butterfly\source\music\目录下。

  7. 优化了配置方式,可以直接在主题配置文件里添加的配置项中进行修改。
    [Blogroot]\_config.butterfly.yml添加如下配置项:

    # 自定义右键菜单
    GalMenu:
    enable: true # true or false 是否开启右键
    css: /css/galmenu.css #css相对路径
    js: /js/galmenu.js #js相对路径
    audio: true # true or false 是否开启点击音乐
    music: /music/galmenu.mp3 #点击引用相对路径
    MenuItem: #菜单链接和标签配置
    item1: 首页
    link1: /
    item2: 时间轴
    link2: /archives/
    item3: 标签
    link3: /tags/
    item4: 分类
    link4: /categories/
    item5: 友人帐
    link5: /link/
    item6: 留言板
    link6: /comments/

进阶教程

添加浏览器动作

前进后退刷新返回顶部等动作直接修改配置项中的link即可实现。例如:

MenuItem: #菜单链接和标签配置
item1: 刷新
link1: javascript:location.reload();
item2: 前进
link2: javascript:history.go(1);
item3: 后退
link3: javascript:history.go(-1);
item4: 返回顶部
link4: '#' #必须用''包起来不然会被识别成注释符

添加随机文章功能

这一功能照搬自HCLonely-Hexo博客美化,这是个全局功能,不局限于butterfly主题。

  1. [Blogroot]\scripts\目录下新建random.js文件:
    hexo.extend.generator.register('random', function (locals) {
    const config = hexo.config.random || {}
    const posts = []
    for (const post of locals.posts.data) {
    if (post.random !== false) posts.push(post.path)
    }
    return {
    path: config.path || 'random/index.html',
    data: `<html><head><script>var posts=${JSON.stringify(posts)};window.open('/'+posts[Math.floor(Math.random() * posts.length)],"_self")</script></head></html>`
    }
    })
  2. 使用/random/即可访问随机文章。
    MenuItem: #菜单链接和标签配置
    item1: 随机文章
    link1: /random/

添加镜像站跳转功能

  1. 使用原生js实现,因为要添加onclick()动作,比起添加配置项还是直接改源码比较快。
  2. Gitee工具人的活用方法可以参考这两篇文章:
  1. [Blogroot]\themes\butterfly\source\js\目录下新建mirror.js文件,记得修改对应域名为你自己的:

    function Mirror() {
    let pathname;
    let hostname;
    let url;
    pathname = window.location.pathname;
    hostname = window.location.hostname;
    // if (hostname === '域名,不带https://协议')
    if (hostname === 'akilar.top') { //如果是主站
    // url = "域名,带https://协议" + pathname;
    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("本地调试,无需跳转");
    }
    }
  2. [Blogroot]\_config.butterfly.yml中引入mirror.js

          inject:
    head:
    bottom:
    + - <script src="/js/mirror.js"></script>
  3. 修改[Blogroot]\themes\butterfly\layout\includes\third-party\galmenu.pug文件
        .ring
    - a.menuItem(href=url_for(theme.GalMenu.MenuItem.link1))
    + a.menuItem(href='javascript:void(0);' onclick='Mirror()')

    bug归纳

  4. 如果使用了gulp对静态资源进行压缩,由于gulp-babel的加密压缩算法问题,可能造成部署到线上以后右键菜单不显示的bug。
    • 解决方案:在[Blogroot]\gulpfile.js中添加屏蔽项跳过对galmenu.js的压缩
              //minify js babel
      gulp.task('compress', () =>
      - gulp.src(['./public/**/*.js', '!./public/**/*.min.js'])
      + gulp.src(['./public/**/*.js', '!./public/**/*.min.js','!./public/galmenu/galmenu.js'])
      .pipe(babel({
      presets: ['@babel/preset-env']
      }))

TO DO

  1. 封装页面,使得跳转镜像站可以直接通过/mirror/链接访问。

  2. 优化图标取值方案,使得图标可以直接在配置项进行设置。