视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
如何利用js实现仿world中批注功能的效果(附代码)
2020-11-27 19:32:21 责编:小采
文档


本篇文章给大家带来的内容是关于如何利用js实现仿world中批注功能的效果(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助。

初入前端,最近在使用word时发现有个批注功能,就想的如何用代码去实现一下

一、大概需求如下:

1.页面整体分为左中右两部分,中间为正文内容区域,右右两侧为注释浏览区域
2.右侧展示的注释内容必须与所需注释词汇在一条线上
3.初始时只显示两行内容,点击时展开全部
4.如果两个被注释的词距离太近,注释部分要求依次排序

二、预设解决方案

1.在注释内容外侧添加一个p,利用p的min-height属性控制注释的位置
2.利用position: absolute绝对定位,动态的生成和改变注释的位置

三、实现过程

在实现上述两种方法的过程中发现,第一种方案在数据量庞大的情况下,会出现bug,页面会奔溃,果断放弃了,选择了第二种方式实现

1.常量部分:

1> args--------->当前文章内容中有注释的词集合
2> notes-------->从库中获取到的注释文本集合
3> rightWrap---->右侧部分注释区域对象
4> leftWrap----->左侧部分注释区域对象

2.方法部分

1> setSite()------------------------------>初始界面加载时确定注释的位置
2> resetTop(elem, type)------------------->在点击时重新设置所有注释的位置
elem:当前被点击的对象
type:(open/close)全部展开或部分展示
3> bindClick(elem, type, selector, fn)---->绑定事件函数
elem:绑定事件的元素
type:绑定的事件类型,例如(click)
selector:动态添加到elem中的元素
fn:绑定事件执行后回调方法

3.整体代码

1> index.html部分代码

<p class="wrap">
 <aside class="left"></aside>
 <article class="center">
 <h3>人世</h3>
 <br />
 <p>使其停下来</p>
 <p>使光影、蜉蝣</p>
 <p><b class="special-0 nleft">众生</b>的所向是什么</p>
 <p>尤以静止方可得出</p>
 <p>我不做空明的阐述</p>
 <p>我是凡人,且一直落在凡尘里</p>
 <p>使云霞似锦吧</p>
 <p>若产出时间的黄金</p>
 <p>时间的<b class="special-1">黄金</b>只能在一颗心里</p>
 <p>播种,<b class="special-2">萌发</b>,成为照耀</p>
 <p>内敛的照耀比及月亮</p>
 <p>我们需做辉光的同谋人</p>
 <p>我们依旧不能成为闪电或是惊雷</p>
 <p>我们只是平凡的形形色色</p>
 <p>为所有悸动欢呼的应该是另一群人</p>
 <p>那些卑微的怯懦的都给我</p>
 <p>我隐在暗处说——</p>
 <p>“这很好!”,是的,你注视我说——</p>
 <p>“你很好!”</p>
 <p>还有可以使其堕落下去使其沦陷下去的吗</p>
 <p>光影、<b class="special-3">蜉蝣</b>、我和你</p>
 <p>和岸边无风也要摇荡的芦苇</p>
 <p>和似乎永不休止的蝉鸣</p>
 <p>和流水</p>
 </article>
 <aside class="right"></aside>
</p>

2> index.css部分代码

.wrap {
 display: flex;
 position: relative;
 width: 100%;
}
article.center {
 flex: 1;
 text-align: justify;
 padding: 20px;
 border-right: 1px solid #ddd;
 border-left: 1px solid #ddd;
}
article.center p {
 line-height: 24px;
}
article.center p b {
 color: red;
}
aside.left, aside.right {
 width: 300px;
 padding: 20px 0;
}
.wrap aside mark {
 background-color: #fff;
 color: #afafaf;
 padding: 0 20px;
 position: absolute; 
 top: 0;
 height: 44px;
 overflow: hidden;
 line-height: 20px;
 font-size: 12px;
 text-align: justify;
 cursor: pointer;
 width: 260px;
}

3> index.js部分代码

;
(function() {
 
// 构造函数
 function View(elem, notes, rightWrap, leftWrap) {
 this.rightWrap = rightWrap;
 this.leftWrap = leftWrap;
 this.args = typeof elem === 'object' ? elem : document.getElementById(elem);
 this.notes = notes === undefined ? [] : notes;
 
 this.init();
 }
 
// 原型
 View.prototype = {
 constructor: View,
 
 init: function() {
 var self = this;
 self.setSite();
 self.bindClick(document.body, 'click', 'mark', function (e) {
 var target = e.target;
 if(this.style.height) {
 this.style.height = '';
 self.resetTop(this, 'close');
 return;
 } else {
 this.style.height = this.scrollHeight +'px';
 self.resetTop(this, 'open');
 }
 });
 },
 
// 设定初始高度
 setSite: function() {
 for(let i = 0; i < this.args.length; i++) {
// 默认新建的批注距离顶部的高度
 var swdTop = 0;
 var addMark = '';
 
// 计算当前批注的高度是否被覆盖,如果被覆盖,进行处理
 if(i > 0) {
 if(this.args[i].offsetTop - this.args[i-1].offsetTop > $('.note-' + (i-1)).height()) {
 swdTop = this.args[i].offsetTop - 2 + 'px';
 } else {
 swdTop = this.args[i-1].offsetTop + $('.note-' + (i-1)).height() - 2 + 'px';
 }
 } else {
 swdTop = this.args[i].offsetTop - 2 + 'px';
 }
 
 if(this.notes.length > 0) {
 addMark = '<mark class="note-' + i + '" style="top:' + swdTop + '">'+ this.args[i].innerHTML +':' + this.notes[i] + '</mark>';
 } else {
 addMark = '';
 }
 
// 将得到的新标签动态添加到容器中
 if(this.args[i].classList.length > 1 && this.args[i].classList[1] === 'nleft' && this.leftWrap !== undefined) {
 this.leftWrap.append(addMark);
 } else {
 this.rightWrap.append(addMark);
 }
 }
 },
 
// 重新设置元素高度
 resetTop: function(elem, type) {
 let index = parseInt(elem.className.substr(elem.className.indexOf('-')+1));
 for(; index < this.args.length-1; index++) {
 var swdNewTop = 0;
 var addTop = [];
 if(this.args[index+1].offsetTop - this.args[index].offsetTop > $('.' + elem.className).height()) {
 console.log('我们不需要执行任何东西了')
 return
 } else {
 if(type === 'open') {
 swdNewTop = this.args[index].offsetTop + $('.' + elem.className).height() + 8 + 'px';
 addTop[index+1] = swdNewTop;
 } else {
 swdNewTop = this.args[index].offsetTop + $('.' + elem.className).height() + 'px';
 }
 $('.note-' + (index+1)).attr('style', 'top:' + swdNewTop);
 return
 }
 }
 },
 
// 绑定元素点击事件
 bindClick: function(elem, type, selector, fn) {
 if(fn === null) {
 fn = selector;
 selector = null;
 }
 elem.addEventListener(type, function(e) {
 var target = e.target;
 if(selector) {
 target = e.target;
 if(target.matches(selector)) {
 fn.call(target, e);
 }
 } else {
 fn(e);
 }
 })
 }
 }
 
// 对外公开方法
 window.View = View;
})();

4.通过扩展方法将拖拽方法扩展到jquery的一个实例方法

(function($) {
 $.fn.extend({
 viewDocument: function(notes, rightWrap, leftWrap) {
 new View(this, notes, rightWrap, leftWrap);
 // 为了保证jQuery所有的方法能够实现链式访问,每个方法的最后必须返回this,即返回jquery的实例
 return this;
 }
 })
})(jQuery);

5.在主界面上的调用方法

// 此内容从数据库中获取,这里只是举个例子
let notes = [
 '山不在高,有仙则名;水不在深,有龙则灵;斯是陋室,惟吾德馨',
 '东边日出西边雨,道是无晴却有晴。一蓑烟雨任平生。桃李不言下自成蹊。会当凌绝顶,一览众山小。莫道不消魂,帘卷黄花瘦。',
 '得不到的永远在骚动,被宠爱的都有恃无恐,玫瑰的红,容易受伤的梦。',
 '青青园中葵,朝露待日晞。阳春布德泽,万物生光辉。常恐秋节至,焜黄华叶衰。百川东到海,何时复西归?少壮不努力,老大徒伤悲!'
 ];
// 获取注释所在的容器
let rightWrap = $('aside.right');
let leftWrap = $('aside.left');
$('.center b').viewDocument(notes, rightWrap, leftWrap);

下载本文
显示全文
专题