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


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 lazyLoad图片延迟加载插件的优化改造方法分享
Aug 13 Javascript
JavaScript验证图片类型(扩展名)的函数分享
May 05 Javascript
jquery左边浮动到一定位置时显示返回顶部按钮
Jun 05 Javascript
JS实现网页滚动条感应鼠标变色的方法
Feb 26 Javascript
JavaScript使用Replace进行字符串替换的方法
Apr 14 Javascript
浅谈jquery中delegate()与live()
Jun 22 Javascript
BootStrap下拉框在firefox浏览器界面不友好的解决方案
Aug 18 Javascript
bootstrap datetimepicker日期插件超详细使用方法介绍
Feb 23 Javascript
JS库之ParticlesJS使用简介
Sep 12 Javascript
JS实现json对象数组按对象属性排序操作示例
May 18 Javascript
react配置antd按需加载的使用
Feb 11 Javascript
Openlayers显示地理位置坐标的方法
Sep 28 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使之能同时支持GIF和JPEG
2006/10/09 PHP
PHP 文件上传源码分析(RFC1867)
2009/10/30 PHP
php imagecreatetruecolor 创建高清和透明图片代码小结
2010/05/15 PHP
thinkPHP模板引擎用法示例
2016/12/08 PHP
PHP hex2bin()函数用法讲解
2019/02/25 PHP
PHP判断是否微信访问的方法示例
2019/03/27 PHP
PHP7导出Excel报ERR_EMPTY_RESPONSE解决方法
2019/04/16 PHP
jQuery操作select下拉框的text值和value值的方法
2014/05/31 Javascript
通过Jquery的Ajax方法读取将table转换为Json
2014/05/31 Javascript
Javascript学习笔记之 对象篇(四) : for in 循环
2014/06/24 Javascript
easyui messager alert 三秒后自动关闭提示的实例
2016/11/07 Javascript
JavaScript数据结构之二叉树的删除算法示例
2017/04/13 Javascript
JavaScript图片处理与合成总结
2018/03/04 Javascript
微信小程序文字显示换行问题
2019/07/28 Javascript
通过js示例讲解时间复杂度与空间复杂度
2019/08/06 Javascript
node.js express捕获全局异常的三种方法实例分析
2019/12/27 Javascript
[11:12]2018DOTA2国际邀请赛寻真——绿色长城OpTic
2018/08/10 DOTA
python生成二维码的实例详解
2017/10/29 Python
python 实现线程之间的通信示例
2020/02/14 Python
python re的findall和finditer的区别详解
2020/11/15 Python
如何在scrapy中集成selenium爬取网页的方法
2020/11/18 Python
Lookfantastic台湾:英国彩妆美发保养购物网
2018/03/26 全球购物
台湾租车首选品牌:IWS艾维士租车
2019/05/03 全球购物
早晨薰衣草在线女性精品店:Morning Lavender
2021/01/04 全球购物
澳大利亚排名第一的露营和户外设备在线零售商:Outbax
2020/05/06 全球购物
Java面试题及答案
2012/09/08 面试题
结构工程研究生求职信
2013/10/13 职场文书
商场消防管理制度
2014/01/12 职场文书
信用卡工资证明格式
2014/09/13 职场文书
2015年综治宣传月活动总结
2015/03/25 职场文书
行政主管岗位职责范本
2015/04/09 职场文书
因家庭原因离职的辞职信范文
2015/05/12 职场文书
电影圆明园观后感
2015/06/03 职场文书
2015年社区国庆节活动总结
2015/07/30 职场文书
幼儿园2016圣诞节活动总结
2016/03/31 职场文书
详细聊聊关于Mysql联合查询的那些事儿
2021/10/24 MySQL