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


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 相关文章推荐
OfflineSave离线保存代码再次发布使用说明
May 23 Javascript
JQuery.closest(),parent(),parents()寻找父结点
Feb 17 Javascript
FF火狐下获取一个元素同类型的相邻元素实现代码
Dec 15 Javascript
兼容IE和FF的图片上传前预览js代码
May 28 Javascript
批量实现面向对象的实例代码
Jul 01 Javascript
js函数获取html中className所在的内容并去除标签
Sep 08 Javascript
jQuery常用的一些技巧汇总
Mar 26 Javascript
Angular4.0中引入laydate.js日期插件的方法教程
Dec 25 Javascript
vue兄弟组件传递数据的实例
Sep 06 Javascript
vue-for循环嵌套操作示例
Jan 28 Javascript
浅谈目前可以使用ES10的5个新特性
Jun 25 Javascript
JavaScript 中的无穷数(Infinity)详解
Feb 13 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分页代码实例
2013/10/24 PHP
PIGCMS 如何关闭聊天机器人
2015/02/12 PHP
thinkphp在低版本Nginx 下支持PATHINFO的方法分享
2016/05/27 PHP
Jquery实现无刷新DropDownList联动实现代码
2010/03/08 Javascript
jQuery validate 中文API 附validate.js中文api手册
2010/07/31 Javascript
jQuery插件开发全解析
2012/10/10 Javascript
jquery实现的可隐藏重现的靠边悬浮层实例代码
2013/05/27 Javascript
解析JavaScript中点号“.”的多义性
2013/12/02 Javascript
在线一元二次方程计算器实例(方程计算器在线计算)
2013/12/22 Javascript
extjs 分页使用jsp传递数据示例
2014/07/29 Javascript
字段太多jquey快速清空表单内容方法
2014/08/21 Javascript
详解用vue.js和laravel实现微信支付
2017/06/23 Javascript
微信小程序开发之改变data中数组或对象的某一属性值
2018/07/05 Javascript
Vue自定义指令封装节流函数的方法示例
2018/07/09 Javascript
2019 年编写现代 JavaScript 代码的5个小技巧(小结)
2019/01/15 Javascript
vue多层嵌套路由实例分析
2019/03/19 Javascript
微信小程序中使用Async-await方法异步请求变为同步请求方法
2019/03/28 Javascript
Angular请求防抖处理第一次请求失效问题
2019/05/17 Javascript
微信小程序swiper禁止用户手动滑动代码实例
2019/08/23 Javascript
Node对CommonJS的模块规范
2019/11/06 Javascript
记一次react前端项目打包优化的方法
2020/03/30 Javascript
vue-quill-editor 自定义工具栏和自定义图片上传路径操作
2020/08/03 Javascript
python清除字符串里非数字字符的方法
2015/07/02 Python
Python元组拆包和具名元组解析实例详解
2018/03/26 Python
python的pytest框架之命令行参数详解(上)
2019/06/27 Python
python中的逆序遍历实例
2019/12/25 Python
jupyter实现重新加载模块
2020/04/16 Python
Regatta官网:英国最受欢迎的户外服装和鞋类品牌
2019/05/01 全球购物
简述安装Slackware Linux系统的过程
2012/01/12 面试题
程序员跳槽必看面试题总结
2013/06/28 面试题
数据员岗位职责
2013/11/19 职场文书
企业职业病防治方案
2014/05/29 职场文书
我的中国梦演讲稿400字
2014/08/19 职场文书
消防志愿者活动方案
2014/08/23 职场文书
大学生党性分析材料
2014/12/19 职场文书
反邪教教育心得体会
2016/01/15 职场文书