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


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 相关文章推荐
用jquery与css打造个性化的单选框和复选框
Oct 20 Javascript
有关于JS辅助函数inherit()的问题
Apr 07 Javascript
js获取url中&quot;?&quot;后面的字串方法
May 15 Javascript
JS实现带鼠标效果的头像及文章列表代码
Sep 27 Javascript
jquery实现具有收缩功能的垂直导航菜单
Feb 16 Javascript
JavaScript数组的栈方法与队列方法详解
May 26 Javascript
Windows环境下npm install 报错: operation not permitted, rename的解决方法
Sep 26 Javascript
JavaScript表单验证的两种实现方法
Feb 11 Javascript
JavaScript中 this 指向问题深度解析
Feb 21 Javascript
10个在JavaScript开发中常遇到的BUG
Dec 18 Javascript
webpack 如何解析代码模块路径的实现
Sep 04 Javascript
原生js实现自定义难度的扫雷游戏
Jan 22 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
paypal即时到账php实现代码
2010/11/28 PHP
php常量详细解析
2015/10/27 PHP
PHP抓取远程图片(含不带后缀的)教程详解
2016/10/21 PHP
php实用代码片段整理
2016/11/12 PHP
基础的prototype.js常用函数及其用法
2007/03/10 Javascript
学习jquery必备 api中英文对照的chm手册 下载
2007/05/03 Javascript
js 判断浏览器使用的语言示例代码
2014/03/22 Javascript
javascript闭包入门示例
2014/04/30 Javascript
JavaScript使用位运算符判断奇数和偶数的方法
2015/06/01 Javascript
connection reset by peer问题总结及解决方案
2016/10/21 Javascript
简单学习vue指令directive
2016/11/03 Javascript
图文详解Javascript中的上下文和作用域
2017/02/15 Javascript
js正则表达式验证密码强度【推荐】
2017/03/03 Javascript
JavaScript for循环 if判断语句(学习笔记)
2017/10/11 Javascript
详解Element-UI中上传的文件前端处理
2019/08/07 Javascript
jquery轻量级数字动画插件countUp.js使用详解
2019/10/17 jQuery
微信小程序实现比较功能的方法汇总(五种方法)
2020/03/07 Javascript
ant design vue 表格table 默认勾选几项的操作
2020/10/31 Javascript
python通过urllib2爬网页上种子下载示例
2014/02/24 Python
Python通过websocket与js客户端通信示例分析
2014/06/25 Python
Python中使用urllib2防止302跳转的代码例子
2014/07/07 Python
python中日志logging模块的性能及多进程详解
2017/07/18 Python
Python基于内置库pytesseract实现图片验证码识别功能
2020/02/24 Python
如何通过安装HomeBrew来安装Python3
2020/12/23 Python
全球性的在线购物网站:Zapals
2017/03/22 全球购物
美国校园市场:OCM
2017/06/08 全球购物
如何估计一张表的大小(假设该表中有1万条数据)
2016/03/27 面试题
会议活动邀请函
2014/01/27 职场文书
法律专业学生的自我评价
2014/02/07 职场文书
英语老师推荐信
2014/02/26 职场文书
潘婷洗发水广告词
2014/03/14 职场文书
机电专业毕业生求职信
2014/07/01 职场文书
初中中等生评语
2014/12/29 职场文书
拯救大兵瑞恩观后感
2015/06/09 职场文书
《别在吃苦的年纪选择安逸》读后感3篇
2019/11/30 职场文书
Vue鼠标滚轮滚动切换路由效果的实现方法
2021/08/04 Vue.js