javascript小说阅读器分页算法的实现

        好久好久不写代码了,也好久没更新博客了,这次就和大家分享一个电子书阅读器分页的算法吧。

        像一些主流的阅读器,如QQ阅读、iReader等,都实现了txt文档分页显示的功能,打开一个txt文档可以快速把文档分割成若干页,每页文字正好铺满屏幕,点击翻屏显示下一页,这样不用操作滚动条,阅读体验更好。那么如何实现txt文件的快速分页呢?如果计算屏幕大小和字体大小算出一屏可以有多少字,这样肯定不行,因为有英文和其他字符比如中文和标点,还有换行回车,所以肯定要先设置一块和显示屏一样大的容器(可变高度),首先根据屏幕大小和字体大小算出一屏可以容纳的字数最大值,从文件读出这些字数,将这段文字写入设置好的那个容器,如果容器高度大于屏幕高度,则减少文字读取的长度,再次试探容器高度是否大于屏幕高度,直到容器高度和屏幕高度一致,则当前屏幕应该显示的内容就是容器内的内容。要是一个字一个字试探效率太低,我改进了算法,每次减去20字,当减到容器高度小于屏幕高度时,在反向微调,添加一个字,这样分页的效率大大提高,点击显示下一页看不出延迟。核心代码如下图

        前些日子写了个阅读器demo,只是实现了文件浏览,打开文件和文件分页功能,阅读器主题设置,阅读历史存档等还没做,因为这些都比较简单了,懒得去做了,大家有兴趣可以在此基础上修改成成品。下面先上效果图再上完整代码,这个是用xml做的视图层,JScript做的逻辑层,可以运行于windows7及以上系统。

首先是效果图:

主题设置目前只做了字体和背景颜色的设置,所有的设置都存在一个对象之中,可以通过读写json文件存储,读写我已经封装到IOMod这个模块了,可以直接拿来用getSetting和setSetting两个方法。

最后编译完成的文件如下:

下面上代码:首先是视图模块viewMod.js

/*应用程序界面模块*/
this.viewMod={
	root:"F:\\电子书",totalNum:0,ramPoint:0,textData:"",S:{},screen:"",
	init:function(screen){
		this.S=ioMod.getSetting();
		/*读取配置,fontSize,color,background,lastRead*/
		var SC=document.querySelector(screen);
		SC.style.fontSize=this.S.fontSize+"px";
		SC.style.color=this.S.color;
		document.body.style.background=this.S.background;
		var SC_BAR=document.querySelector("readerViewBar");
		SC_BAR.style.borderBottom="1px solid "+this.S.color;
		SC_BAR.style.color=this.S.color;
		this.screenX=SC.offsetWidth;
		this.screenY=SC.offsetHeight;
		this.screen=screen;
	},
	print:function(item,data){
		document.querySelector(item).innerHTML=data;
	},
	printT:function(item,data){
		document.querySelector(item).innerText=data;
	},
	fileList:function(folder){
		folder=(folder==undefined)?this.root:folder;
		/*文件夹和电子书列表的显示*/
		var lastFolder;
		if(folder.indexOf("\\")<0){
			lastFolder=folder;
		}else{
			lastFolder=folder.split("\\");
			lastFolder.splice(lastFolder.length-1,1);
			lastFolder=lastFolder.join("\\");
		}
		var view="<bookItem onclick=\"viewMod.fileList('"+lastFolder.replace(/\\/g,"\\\\")+"')\"><<返回上级</bookItem>";
		var data=ioMod.getFolderList(folder);
		for(var i in data){
			view+="<bookItem onclick=\"viewMod.fileList('"+data[i].url.replace(/\\/g,"\\\\")+"')\"><img src='./src/folder.png'/>";
			view+=data[i].name;
			view+="</bookItem>";
		}
		data=ioMod.getBookList(folder);
		for(var i in data){
			view+="<bookItem onclick=\"viewMod.openBook('"+data[i].url.replace(/\\/g,"\\\\")+"','"+data[i].name+"')\"><img src='./src/app.ico'/>";
			view+=data[i].name;
			view+="</bookItem>";
		}
		this.print("bookFileList",view);
	},
	openBook:function(url,name){
		/*打开文件*/
		this.ramPoint=0;
		this.textData=ioMod.openFile(url);
		var data=this.getTextData("next");
		this.printT(this.screen,data);
		this.printT("titleBar","文件->"+name);
	},
	getTextData:function(direction){
		/*分页*/
		if(document.querySelector("SYS_TEXT_SCREEN")==null){
			var SYS_TEXT_SC=document.createElement("SYS_TEXT_SCREEN");
			SYS_TEXT_SC.style.position="absolute";
			SYS_TEXT_SC.style.top="0px";
			SYS_TEXT_SC.style.left="0px";
			SYS_TEXT_SC.style.visibility="hidden";
			document.body.appendChild(SYS_TEXT_SC);
		}//创建文本读取容器
		var SYS_TEXT=document.querySelector("SYS_TEXT_SCREEN");
		SYS_TEXT.style.width=this.screenX+"px";
		SYS_TEXT.style.fontSize=this.S.fontSize+"px";
		var ramLength=Math.round((this.screenX/this.S.fontSize)*(this.screenY/this.S.fontSize));
		ramLength=ramLength>1500?1500:ramLength;
		var data=this.textData;
		var ramLength=data.length>ramLength?ramLength:data.length;//每页大约的文字长度
		if(direction=="next"){
			SYS_TEXT.innerText=data.substr(this.ramPoint,ramLength);
			while(SYS_TEXT.offsetHeight>this.screenY){
				ramLength-=20;
				SYS_TEXT.innerText=data.substr(this.ramPoint,ramLength);
			}
			while(SYS_TEXT.offsetHeight<this.screenY){
				ramLength+=1;
				SYS_TEXT.innerText=data.substr(this.ramPoint,ramLength);
			}
			ramLength-=1;
			SYS_TEXT.innerText=SYS_TEXT.innerText.substr(0,ramLength);
			this.ramPoint+=ramLength;
		}else{
			ramLength=this.ramPoint-ramLength<=0?this.ramPoint:ramLength;
			SYS_TEXT.innerText=data.substr(this.ramPoint-ramLength,ramLength);
			var IS_LONG=false;
			while(SYS_TEXT.offsetHeight>this.screenY){
				IS_LONG=true;
				ramLength-=20;
				SYS_TEXT.innerText=data.substr(this.ramPoint-ramLength,ramLength);
			}
			if(IS_LONG){
				while(SYS_TEXT.offsetHeight<this.screenY){
					ramLength+=1;
					SYS_TEXT.innerText=data.substr(this.ramPoint-ramLength,ramLength);
				}
				ramLength-=1;
			}
			this.ramPoint-=ramLength;
		}
		this.printT("speedBar","进度"+(this.ramPoint/this.textData.length*100).toFixed(2)+"%");
		return SYS_TEXT.innerText;
	},
	nextPage:function(){
		/*下一页*/
		if(this.ramPoint>this.textData.length){

		}else{
			var data=this.getTextData("next");
			this.printT(this.screen,data);
		}
	},
	lastPage:function(){
		/*上一页*/
		if(this.ramPoint<=0){

		}else{
			var data=this.getTextData("last");
			this.printT(this.screen,data);
		}
	},
	diaLog:function(view){
		return showModalDialog(view+".view");
	},
	setColors:function(type){
		var color=this.diaLog("colors");
		if(color==undefined){
			return;
		}
		if(type=="color"){
			this.S.color=color;
			document.querySelector(this.screen).style.color=this.S.color;
			document.querySelector("readerViewBar").style.color=this.S.color;
			document.querySelector("readerViewBar").style.borderBottom="1px solid "+this.S.color;
			document.querySelector("fontColor").style.color=this.S.color;
		}else{
			this.S.background=color;
			document.body.style.background=this.S.background;
			document.querySelector("backgroundColor").style.color=this.S.background;
		}
	}
};

其次是文件读写模块ioMod.js

/*文件读写模块*/
this.ioMod={
	getBookList:function(folder){
		var data=[];
		/*取得书籍列表*/
		var fso=new ActiveXObject("Scripting.FileSystemObject");
		var f=fso.GetFolder(folder);
		var fc=new Enumerator(f.files);
		var books=/(.txt|.pdf)/i;
		for (; !fc.atEnd(); fc.moveNext())
		{
			if(books.test(fc.item())==true){
				data.push({url:""+fc.item(),name:fc.item().name});
			}
		}
		return data;
	},
	getFolderList:function(folder){
		/*取得文件夹列表*/
		var data=[];
		var fso=new ActiveXObject("Scripting.FileSystemObject");
		var f=fso.GetFolder(folder);
		var fc=new Enumerator(f.SubFolders);
		var str="\r\n";
		for (; !fc.atEnd(); fc.moveNext())
		{
			data.push({url:""+fc.item(),name:fc.item().name});
		}
		return data;
	},
	openFile:function(url){
		/*读取文件流*/
		var fso=new ActiveXObject("Scripting.FileSystemObject");
		var fl=fso.OpenTextFile(url,1);
		doc=fl.ReadAll();
		fl.Close();
		return doc;
	},
	getSetting:function(){
		/*读取配置*/
		var fso=new ActiveXObject("Scripting.FileSystemObject");
		var fl=fso.OpenTextFile("setting.json",1);
		data=fl.ReadAll();
		data=JSON.parse(data);
		fl.Close();
		return data;
	},
	setSetting:function(data){
		/*写入配置*/
		data=JSON.stringify(data);
		var fso=new ActiveXObject("Scripting.FileSystemObject");
		var tf=fso.CreateTextFile("setting.json",true);
		tf.Write(data);
		tf.Close();
	},
	write:function(data,fileName){
		/*写入配置*/
		var fso=new ActiveXObject("Scripting.FileSystemObject");
		var tf=fso.CreateTextFile(fileName,true);
		tf.Write(data);
		tf.Close();
	},
}

然后是主视图main.hta

<!doctype html>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=9" >
<title>文本阅读器v1.0</title>
<!-- hta应用程序描述 -->
<hta:application 
id=musicPlayer 
scroll=no 
innerborder=no 
icon=./src/app.ico
contextMenu=no
selection=no
windowState=maximize
>
<style>body{scrollbar-highlight-color:#000;}</style>
<body style="background: #ccc">
	<bookListView
	style="position:absolute;left:0px;top:0px;width:24%;height:100%;min-width:340px;min-height:700px;filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#666666', endColorstr='#000000',GradientType=0 ); ">
		<bookListBar style="position: absolute;left:10px;top:10px;right:10px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;background:#ccc;">
			最近阅读
		</bookListBar>
		<style>
			settingItem,bookItem{display: block;padding: 5px;margin-top: 5px;font-size: 22px;color:#fff;border:1px solid #fff;border-radius: 10px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;cursor:pointer; 
			}
			settingItem:hover,bookItem:hover{display: block;padding: 5px;margin-top: 5px;font-size: 22px;color:#fff;border:1px solid #fff;border-radius: 10px;overflow: hidden;text-overflow: ellipsis;white-space: nowrap;cursor:pointer;background:#38f; 
			}	
			bookItem img{width: 22px;height: 22px;}
		</style>
		<readHistoryList style="position: absolute;left:10px;top:50px;right:10px;height:100px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;color:#fff;overflow-y: scroll;">
			<bookItem><img src='./src/app.ico'/>测试文件.pdf</bookItem>
		</readHistoryList>
		<bookListBar style="position: absolute;left:10px;top:180px;right:10px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;background:#ccc;">
			浏览文件
		</bookListBar>
		<bookFileList style="position: absolute;left:10px;top:220px;right:10px;bottom:20px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;color:#fff;overflow-y: scroll;">

		</bookFileList>
	</bookListView>
	<readerViewBar style="position: absolute;left:25%;right: 25%;min-width:660px;top:5px;height:25px;padding:5px 0px 0px 5px;">
		<speedBar>进度:</speedBar>&nbsp;&nbsp;
		<titleBar>文件-></titleBar>
	</readerViewBar>
	<readerView style="position: absolute;left:25%;right: 25%;min-width:660px;top:40px;bottom:20px;" onclick="viewMod.nextPage()" oncontextMenu="viewMod.lastPage();">
		
	</readerView>
	<settingListView
	style="position:absolute;right:0px;top:0px;width:24%;height:100%;min-width:340px;min-height:700px;filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#666666', endColorstr='#000000',GradientType=0 );">
		<themeBar style="position: absolute;left:10px;top:10px;right:10px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;background:#ccc;">
			主题设置
		</themeBar>
		<themeList style="position: absolute;left:10px;top:50px;right:10px;height:100px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;color:#fff;">
			<span onClick=viewMod.setColors("color")>字体颜色:</span><fontColor style="color: green;">■</fontColor><br>
			<span  onClick=viewMod.setColors("background")>背景颜色:</span><backgroundColor style="color: #000;">■</backgroundColor><br>
			<span>字体大小:24px</span><br>
			<span>字体设置:宋体</span>
		</themeList>
		<functionBar style="position: absolute;left:10px;top:180px;right:10px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;background:#ccc;">
			系统设置
		</functionBar>
		<functionList style="position: absolute;left:10px;top:220px;right:10px;bottom:20px;padding:5px;font-size:25px;border:1px solid #fff;border-radius:5px;font-size:22px;color:#fff;overflow-y: scroll;">
			<settingItem>添加书签</settingItem>
			<settingItem>书签管理</settingItem>
		</functionList>
	</settingListView>
</body>
<script src="./ioMod.js"></script>
<script src="./pageMod.js"></script>
<script src="./viewMod.js"></script>
<script>
this.onload=function(){
	viewMod.init("readerView");
	viewMod.fileList();
}
</script>

然后是颜色选择器视图colors.view

<!DOCTYPE html>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=9" >
<title>文本阅读器【颜色选择控件】</title>
<body>
</body>
<script>
this.colors=[{"name":"浅粉色","value":"#FFB6C1"},{"name":"粉红","value":"#FFC0CB"},{"name":"猩红","value":"#DC143C"},{"name":"脸红的淡紫色","value":"#FFF0F5"},{"name":"苍白的紫罗兰红色","value":"#DB7093"},{"name":"热情的粉红","value":"#FF69B4"},{"name":"深粉色","value":"#FF1493"},{"name":"适中的紫罗兰红色","value":"#C71585"},{"name":"兰花的紫色","value":"#DA70D6"},{"name":"蓟","value":"#D8BFD8"},{"name":"李子","value":"#DDA0DD"},{"name":"紫罗兰","value":"#EE82EE"},{"name":"洋红","value":"#FF00FF"},{"name":"灯笼海棠(紫红色)","value":"#FF00FF"},{"name":"深洋红色","value":"#8B008B"},{"name":"紫色","value":"#800080"},{"name":"适中的兰花紫","value":"#BA55D3"},{"name":"深紫罗兰色","value":"#9400D3"},{"name":"深兰花紫","value":"#9932CC"},{"name":"靛青","value":"#4B0082"},{"name":"深紫罗兰的蓝色","value":"#8A2BE2"},{"name":"适中的紫色","value":"#9370DB"},{"name":"适中的板岩暗蓝灰色","value":"#7B68EE"},{"name":"板岩暗蓝灰色","value":"#6A5ACD"},{"name":"深岩暗蓝灰色","value":"#483D8B"},{"name":"薰衣草花的淡紫色","value":"#E6E6FA"},{"name":"幽灵的白色","value":"#F8F8FF"},{"name":"纯蓝","value":"#0000FF"},{"name":"适中的蓝色","value":"#0000CD"},{"name":"午夜的蓝色","value":"#191970"},{"name":"深蓝色","value":"#00008B"},{"name":"海军蓝","value":"#000080"},{"name":"宝蓝","value":"#4169E1"},{"name":"矢车菊的蓝色","value":"#6495ED"},{"name":"淡钢蓝","value":"#B0C4DE"},{"name":"浅石板灰","value":"#778899"},{"name":"石板灰","value":"#708090"},{"name":"道奇蓝","value":"#1E90FF"},{"name":"爱丽丝蓝","value":"#F0F8FF"},{"name":"钢蓝","value":"#4682B4"},{"name":"淡蓝色","value":"#87CEFA"},{"name":"天蓝色","value":"#87CEEB"},{"name":"深天蓝","value":"#00BFFF"},{"name":"淡蓝","value":"#ADD8E6"},{"name":"火药蓝","value":"#B0E0E6"},{"name":"军校蓝","value":"#5F9EA0"},{"name":"蔚蓝色","value":"#F0FFFF"},{"name":"淡青色","value":"#E1FFFF"},{"name":"苍白的绿宝石","value":"#AFEEEE"},{"name":"青色","value":"#00FFFF"},{"name":"水绿色","value":"#00FFFF"},{"name":"深绿宝石","value":"#00CED1"},{"name":"深石板灰","value":"#2F4F4F"},{"name":"深青色","value":"#008B8B"},{"name":"水鸭色","value":"#008080"},{"name":"适中的绿宝石","value":"#48D1CC"},{"name":"浅海洋绿","value":"#20B2AA"},{"name":"绿宝石","value":"#40E0D0"},{"name":"绿玉","value":"#7FFFAA"},{"name":"适中的碧绿色","value":"#00FA9A"},{"name":"适中的春天的绿色","value":"#F5FFFA"},{"name":"薄荷奶油","value":"#00FF7F"},{"name":"春天的绿色","value":"#3CB371"},{"name":"海洋绿","value":"#2E8B57"},{"name":"蜂蜜","value":"#F0FFF0"},{"name":"淡绿色","value":"#90EE90"},{"name":"苍白的绿色","value":"#98FB98"},{"name":"深海洋绿","value":"#8FBC8F"},{"name":"酸橙绿","value":"#32CD32"},{"name":"酸橙色","value":"#00FF00"},{"name":"森林绿","value":"#228B22"},{"name":"纯绿","value":"#008000"},{"name":"深绿色","value":"#006400"},{"name":"查特酒绿","value":"#7FFF00"},{"name":"草坪绿","value":"#7CFC00"},{"name":"绿黄色","value":"#ADFF2F"},{"name":"橄榄土褐色","value":"#556B2F"},{"name":"米色(浅褐色)","value":"#6B8E23"},{"name":"浅秋麒麟黄","value":"#FAFAD2"},{"name":"象牙色","value":"#FFFFF0"},{"name":"浅黄色","value":"#FFFFE0"},{"name":"纯黄","value":"#FFFF00"},{"name":"橄榄","value":"#808000"},{"name":"深卡其布","value":"#BDB76B"},{"name":"柠檬薄纱","value":"#FFFACD"},{"name":"灰秋麒麟","value":"#EEE8AA"},{"name":"卡其布","value":"#F0E68C"},{"name":"金","value":"#FFD700"},{"name":"玉米色","value":"#FFF8DC"},{"name":"秋麒麟","value":"#DAA520"},{"name":"花的白色","value":"#FFFAF0"},{"name":"老饰带","value":"#FDF5E6"},{"name":"小麦色","value":"#F5DEB3"},{"name":"鹿皮鞋","value":"#FFE4B5"},{"name":"橙色","value":"#FFA500"},{"name":"番木瓜","value":"#FFEFD5"},{"name":"漂白的杏仁","value":"#FFEBCD"},{"name":"Navajo白","value":"#FFDEAD"},{"name":"古代的白色","value":"#FAEBD7"},{"name":"晒黑","value":"#D2B48C"},{"name":"结实的树","value":"#DEB887"},{"name":"(浓汤)乳脂,番茄等","value":"#FFE4C4"},{"name":"深橙色","value":"#FF8C00"},{"name":"亚麻布","value":"#FAF0E6"},{"name":"秘鲁","value":"#CD853F"},{"name":"桃色","value":"#FFDAB9"},{"name":"沙棕色","value":"#F4A460"},{"name":"巧克力","value":"#D2691E"},{"name":"马鞍棕色","value":"#8B4513"},{"name":"海贝壳","value":"#FFF5EE"},{"name":"黄土赭色","value":"#A0522D"},{"name":"浅鲜肉(鲑鱼)色","value":"#FFA07A"},{"name":"珊瑚","value":"#FF7F50"},{"name":"橙红色","value":"#FF4500"},{"name":"深鲜肉(鲑鱼)色","value":"#E9967A"},{"name":"番茄","value":"#FF6347"},{"name":"薄雾玫瑰","value":"#FFE4E1"},{"name":"鲜肉(鲑鱼)色","value":"#FA8072"},{"name":"雪","value":"#FFFAFA"},{"name":"淡珊瑚色","value":"#F08080"},{"name":"玫瑰棕色","value":"#BC8F8F"},{"name":"印度红","value":"#CD5C5C"},{"name":"纯红","value":"#FF0000"},{"name":"棕色","value":"#A52A2A"},{"name":"耐火砖","value":"#B22222"},{"name":"深红色","value":"#8B0000"},{"name":"栗色","value":"#800000"},{"name":"纯白","value":"#FFFFFF"},{"name":"白烟","value":"#F5F5F5"},{"name":"Gainsboro","value":"#DCDCDC"},{"name":"浅灰色","value":"#D3D3D3"},{"name":"银白色","value":"#C0C0C0"},{"name":"深灰色","value":"#A9A9A9"},{"name":"灰色","value":"#808080"},{"name":"暗淡的灰色","value":"#696969"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"},{"name":"纯黑","value":"#000000"}];
var str="<table border=1 style='width: 100%;font-size:25px;cursor: pointer;'><tr>";
for(var i in colors){
	if(i%12==0){
		str+="</tr><tr>";
	}
	str+="<td onclick='setColor(this)' title='"+colors[i].name+"' style='background:"+colors[i].value+"'>&nbsp;&nbsp;&nbsp;&nbsp;</td>";
}
str+="</tr>";
document.body.innerHTML=str;
this.setColor=function(obj){
	window.returnValue=obj.style.background;
	window.close();
}
</script>

基础的功能已完成,剩下的只是添加功能了。暂时没添加对pdf的支持,可以使用“xpdf-chinese-simplified”库将pdf转换为txt在进行读取,还可以添加对SAPI的支持,使软件具有朗读电子书的功能。

  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值