在博客园博文中添加自定义右键菜单的方法详解


Posted in Javascript onFebruary 05, 2020

页面设计

首先将这三个功能以一个列表<ul>的形式放置。鼠标移入时样式改变,移出时还原

<style>
body{margin: 0;}
ul{
  margin: 0;
  padding: 0;
  list-style: none;
}
.list{
  width: 100px;
  text-align: center;
  cursor: pointer;
  font:20px/40px '宋体';
  background-color: #eee;
}
.in:hover{
  background-color: lightblue;
  color: white;
  font-weight:bold;
}
</style>
<ul id="list" class="list">
  <li class="in">顶部</li>
  <li class="in">点赞</li>
  <li class="in">评论</li>
</ul>

菜单逻辑

菜单逻辑共包括阻止默认行为、显隐效果和位置判断三个部分

默认行为

通常,点击右键时,会弹出浏览器的默认右侧菜单

通过return false可以实现阻止默认行为的效果,当然也可以使用preventDefault()和returnValue,详细信息移步至此

document.oncontextmenu = function(){
  return false;
}

显隐

右键菜单默认隐藏,点击右键时显示,点击左键时再隐藏

关于元素显隐,个人总结过共9种思路,本文就用最简单的display属性

<div id="test" style="height: 100px;width: 100px;background-color: pink;"></div>
<script>
document.onclick = function(){
  test.style.display = 'none';
}
document.oncontextmenu = function(){
  test.style.display = 'block';
  return false;
}
</script>

位置判断

鼠标对象共有6对坐标位置信息,若把右键菜单设置为fixed固定定位,则选择clientX/Y即可

一般地,右键菜单的左上角位置应该是当前鼠标的坐标处

但是,还有另外2种情况需要考虑

【1】如果鼠标的位置到视口底部的位置小于菜单的高度,则鼠标的位置为菜单的底部位置

【2】如果鼠标的位置到视口右侧的位置小于菜单的宽度,则视口的右侧为菜单的右侧

元素的尺寸信息共有偏移尺寸offset、可视区尺寸client和滚动尺寸scroll,此时菜单的宽高应该为偏移尺寸offsetWidth/offsetHeight(全尺寸包含width、padding、border)

<div id="test" style="position:fixed;height: 100px;width: 100px;background-color: pink;"></div>
<script>
document.onclick = function(){
  test.style.display = 'none';
}
document.oncontextmenu = function(e){
  e = e || event;
  test.style.left = e.clientX + 'px';
  test.style.top = e.clientY + 'px';
  //注意,由于加法、减法的优先级高于大于、小于的优先级,所以不用加括号,详细情况移步至此
  if(document.documentElement.clientHeight - e.clientY < test.offsetHeight){
    test.style.top = e.clientY - test.offsetHeight + 'px';
  }
  if(document.documentElement.clientWidth - e.clientX < test.offsetWidth){
    test.style.left = document.documentElement.clientWidth - test.offsetHeight + 'px';
  }
  test.style.display = 'block';
  return false;
}
</script>

功能实现

共用有回到顶部、点赞和评论三个功能需要实现

回到顶部

回到顶部共有5种实现方法,下面使用可读写的scrollTop属性进行效果实现

<body style="height: 3000px;">
<button id="test" style="position:fixed;right:10px;bottom:10px;">回到顶部</button>
<script>
var timer = null;
test.onclick = function(){
  cancelAnimationFrame(timer);
  timer = requestAnimationFrame(function fn(){
    var oTop = document.body.scrollTop || document.documentElement.scrollTop;
    if(oTop > 0){
      document.body.scrollTop = document.documentElement.scrollTop = oTop - 160;
      timer = requestAnimationFrame(fn);
    }else{
      cancelAnimationFrame(timer);
    }  
  });
}
</script>
</body>

但是,上面的代码有一个问题,就是当页面内容较多时,回到顶部的动画效果将持续很长时间。因此,使用时间版的运动更为合适,假设回到顶部的动画效果共运动500ms,则代码如下所示

<body style="height: 2000px;">
<button id="test" style="position:fixed;right:10px;bottom:10px;">回到顶部</button>
<script>
var timer = null;
test.onclick = function(){
  cancelAnimationFrame(timer);
  //获取当前毫秒数
  var startTime = +new Date();   
  //获取当前页面的滚动高度
  var b = document.body.scrollTop || document.documentElement.scrollTop;
  var d = 500;
  var c = b;
  timer = requestAnimationFrame(function func(){
    var t = d - Math.max(0,startTime - (+new Date()) + d);
    document.documentElement.scrollTop = document.body.scrollTop = t * (-c) / d + b;
    timer = requestAnimationFrame(func);
    if(t == d){
     cancelAnimationFrame(timer);
    }
  });
}
</script>
</body>

点赞

点赞函数是博客园自己写的,我们看不到内部函数也无法使用。如果想在右键菜单中使用点赞功能,就需要模拟点击事件。点击右键菜单中的点赞项时,触发博客园的自带的点赞项的click事件

由下图可知,点赞函数加在<div class="diggit">上

由一个小例子来说明模拟点击事件如何实现

点击按钮1时,显示1;点击按钮2时,也要实现同样的功能

<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<div id="result" style="height: 30px;width: 100px;background-color: pink;"></div>
<script>
btn1.onclick= function(){
  result.innerHTML += '1';
}
btn2.onclick = btn1.onclick;
</script>
如法炮制 
<div id="test">点赞</div>
<script>
window.onload = function(){
test.onclick = document.getElementById('div_digg').children[0].onclick;  
}
</script>

增加获取最新点赞数的功能

当id为'menuFavour'的div元素被点击时,更新点赞数。但,由于从服务器获取最新数据以及相关元素的内容发生变化,都需要时间,所以增加2秒的延迟

<div id="menuFavour">点赞(<span id="favourNum">0</span>赞)</div>
<script>
//模拟原始点赞按钮的点击事件
menuFavour.onclick = document.getElementById('div_digg').children[0].onclick; 
//获取赞成数的函数
function getfavourNum(){
  favourNum.innerHTML = document.getElementById('digg_count').innerHTML;  
}
//页面载入时获取赞成数
getfavourNum();
//点击菜单中的赞成项后,再获取最新的赞成数
menuFavour.addEventListener('click',function(){
  setTimeout(function(){
    getfavourNum();
  },2000); 
})
</script>

评论

点击右键菜单中的评论项时,页面定位到评论区的位置

由图中可知,评论区为<div id="comment_form_container">

将元素置于可视区域内有很多方法,如scrollTo()、scrollBy()、通过scrollTop计算、scrollIntoView()方法等等,详细情况移步至此

下面利用scrollIntoView()方法滚动当前元素,进入浏览器的可见区域

<div id="test">评论</div>
<script>
window.onload = function(){
  test.onclick = function(){
    document.getElementById('comment_form_container').scrollIntoView();
  }
}
</script>

完整源码

将HTML结构和CSS样式写成javascript生成的行为,最终形成一份js文件,代码如下

//requestAnimationFrame兼容写法
if(!window.requestAnimationFrame){
  var lastTime = 0;
  window.requestAnimationFrame = function(callback){
    var currTime = new Date().getTime();
    var timeToCall = Math.max(0,16.7-(currTime - lastTime));
    var id = window.setTimeout(function(){
      callback(currTime + timeToCall);
    },timeToCall);
    lastTime = currTime + timeToCall;
    return id;
  }
}
if (!window.cancelAnimationFrame) {
  window.cancelAnimationFrame = function(id) {
    clearTimeout(id);
  };
}
//事件处理程序兼容写法
function addEvent(target,type,handler){
  if(target.addEventListener){
    target.addEventListener(type,handler,false);
  }else{
    target.attachEvent('on'+type,function(event){
      return handler.call(target,event);
    });
  }
}
/*******生成元素*******/
var list = document.createElement('ul');
list.id = 'list';
list.innerHTML = '<li id="menuTop">回到顶部</li>\
  <li id="menuFavour">点赞(<span id="favourNum">0</span>赞)</li>\
  <li id="menuCommand">评论</li>';
document.body.appendChild(list);
/*******添加样式**********/
function loadStyles(str){
  var style = document.createElement("style");
  style.type = "text/css";
  try{
    style.innerHTML = str;
  }catch(ex){
    style.styleSheet.cssText = str;
  }
  var head = document.getElementsByTagName('head')[0];
  head.appendChild(style); 
}
loadStyles("#list{margin: 0!important;\
  padding: 0!important;\
  width: 120px;\
  text-align: center;\
  cursor: pointer;\
  font:20px/40px '宋体';\
  background-color: #eee;\
  position:fixed;\
  display:none;}\
  #list li{list-style:none!important;}\
#list li:hover{background-color: lightblue;color: white;font-weight:bold;}");    
//DOM结构稳定后,再操作
addEvent(window,'load', contextMenuLoad);
function contextMenuLoad(){
  /********显示和隐藏菜单***********/
  addEvent(document,'click',function(){
    list.style.display = 'none';
  })
  //右键点击时,菜单显示
  document.oncontextmenu = function(e){
    e = e || event;
    //通常情况下,菜单的位置就是鼠标的位置
    list.style.left = e.clientX + 'px';
    list.style.top = e.clientY + 'px';
    //当鼠标的位置到视口底部的位置小于菜单的高度,则鼠标的位置为菜单的底部位置
    if(document.documentElement.clientHeight - e.clientY < list.offsetHeight){
      list.style.top = e.clientY - list.offsetHeight + 'px';
    }
    //当鼠标的位置到视口右侧的位置小于菜单的宽度,则视口的右侧为菜单的右侧
    if(document.documentElement.clientWidth - e.clientX < list.offsetWidth){
      list.style.left = document.documentElement.clientWidth - list.offsetHeight + 'px';
    }
    list.style.display = 'block';
    //点击右键的同时按下ctrl键,那么将显示默认右键菜单
    if(e.ctrlKey){
      list.style.display = 'none';
    //如果只是点击右键,则显示自定义菜单
    }else{
      return false;
    }    
  }  
  /*********回到顶部功能*********/
  var timer = null;  
  menuTop.onclick = function(){
    cancelAnimationFrame(timer);
    //获取当前毫秒数
    var startTime = +new Date(); 
    //获取当前页面的滚动高度
    var b = document.body.scrollTop || document.documentElement.scrollTop;
    var d = 500;
    var c = b; 
    timer = requestAnimationFrame(function func(){
      var t = d - Math.max(0,startTime - (+new Date()) + d);
    document.documentElement.scrollTop = document.body.scrollTop = t * (-c) / d + b;
    timer = requestAnimationFrame(func);
    if(t == d){
     cancelAnimationFrame(timer);
    } 
    });
  };
  /*********点赞功能**********/
  //模拟原始点赞按钮的点击事件
  var digg = document.getElementById('div_digg');
  if(digg){
    menuFavour.onclick = digg.children[0].onclick;      
  }
  //获取赞成数的函数
  function getfavourNum(){
    if(digg){
      favourNum.innerHTML = digg.children[0].children[0].innerHTML;
    }      
  }
  //页面载入时获取赞成数
  getfavourNum();
  if(menuFavour.addEventListener){
    menuFavour.addEventListener('click',function(){
      setTimeout(function(){
        getfavourNum();
      },2000);
    })  
  }else{
    menuFavour.attachEvent('onclick',function(){
      setTimeout(function(){
        getfavourNum();
      },2000);
    })    
  }
  /*********评论功能*********/
  menuCommand.onclick = function(){
    document.getElementById('comment_form_container').scrollIntoView();
  }
}

更多关于在博客园中添加代码的文章请点击下面的相关链接

Javascript 相关文章推荐
Firebug 字幕文件JSON地址获取代码
Oct 28 Javascript
跟我一起学写jQuery插件开发方法(附完整实例及下载)
Apr 01 Javascript
JQ获取动态加载的图片大小的正确方法分享
Nov 08 Javascript
提高jQuery性能优化的技巧
Aug 03 Javascript
Javascript控制div属性动态变化实例分析
Oct 08 Javascript
js简单实现调整网页字体大小的方法
Jul 23 Javascript
JavaScript prototype属性详解
Oct 25 Javascript
最常见和最有用的字符串相关的方法详解
Feb 06 Javascript
使用 Vue.js 仿百度搜索框的实例代码
May 09 Javascript
详解React中setState回调函数
Jun 14 Javascript
vue路由守卫及路由守卫无限循环问题详析
Sep 05 Javascript
vue 动态设置img的src地址无效,npm run build 后找不到文件的解决
Jul 26 Javascript
Vue中多元素过渡特效的解决方案
Feb 05 #Javascript
Vue路由管理器Vue-router的使用方法详解
Feb 05 #Javascript
Vue的状态管理vuex使用方法详解
Feb 05 #Javascript
浅谈Vue组件单元测试究竟测试什么
Feb 05 #Javascript
VUE中使用HTTP库Axios方法详解
Feb 05 #Javascript
Vue获取页面元素的相对位置的方法示例
Feb 05 #Javascript
vue.js使用v-model实现父子组件间的双向通信示例
Feb 05 #Javascript
You might like
一个PHP分页类的代码
2011/05/18 PHP
php中0,null,empty,空,false,字符串关系的详细介绍
2013/06/20 PHP
php使用socket post数据到其它web服务器的方法
2015/06/02 PHP
PHP遍历目录文件的常用方法小结
2017/02/03 PHP
Prototype ObjectRange对象学习
2009/07/19 Javascript
兼容IE与firefox火狐的回车事件(js与jquery)
2010/10/20 Javascript
JS模拟实现Select效果代码
2015/09/24 Javascript
详解Angular的内置过滤器和自定义过滤器【推荐】
2016/12/26 Javascript
vue2.0在table中实现全选和反选的示例代码
2017/11/04 Javascript
原生JS实现的多个彩色小球跟随鼠标移动动画效果示例
2018/02/01 Javascript
Vue中使用sass实现换肤功能
2018/09/07 Javascript
laydate时间日历插件使用方法详解
2018/11/14 Javascript
微信小程序实现写入读取缓存详解
2019/08/30 Javascript
如何通过JS实现转码与解码
2020/02/21 Javascript
Python continue语句用法实例
2014/03/11 Python
利用Python中的pandas库对cdn日志进行分析详解
2017/03/07 Python
Python之批量创建文件的实例讲解
2018/05/10 Python
python实现ID3决策树算法
2018/08/29 Python
Python多项式回归的实现方法
2019/03/11 Python
在交互式环境中执行Python程序过程详解
2019/07/12 Python
Django1.11配合uni-app发起微信支付的实现
2019/10/12 Python
python实现的读取网页并分词功能示例
2019/10/29 Python
python通过安装itchat包实现微信自动回复收到的春节祝福
2020/01/19 Python
Python编程快速上手——Excel表格创建乘法表案例分析
2020/02/28 Python
使用Keras训练好的.h5模型来测试一个实例
2020/07/06 Python
pycharm激活方法到2099年(激活流程)
2020/09/22 Python
python 发送邮件的示例代码(Python2/3都可以直接使用)
2020/12/03 Python
美国波西米亚风格精品店:South Moon Under
2019/10/26 全球购物
进程的查看和调度分别使用什么命令
2013/12/14 面试题
工程部经理岗位职责
2013/12/08 职场文书
组织关系转移介绍信
2014/01/16 职场文书
团购业务员岗位职责
2014/03/15 职场文书
党干部专题民主生活会对照检查材料思想汇报
2014/10/06 职场文书
检讨书范文500字
2015/01/28 职场文书
Python3的进程和线程你了解吗
2022/03/16 Python
Win11运行cmd提示“请求的操作需要提升”的两种解决方法
2022/07/07 数码科技