还记得曾经秉烛夜读,抱着手机览过一部又一部小说时的情形。我们用过多少种电子阅读器,而你又钟情于哪一款呐?而如今作为程序员,是不是应该用一款自己做出来的阅读器去浏览小说呐?这定然是必须的。这一次,让我们动手写一个自己的电子阅读器吧。

我们用canvas来绘制电子屏。主要完成了以下几个功能 1.绘制下一页 2.绘制上一页 简单吧,就这俩功能,代码已上传github,大家可以下载试试看效果。

github地址:https://github.com/lzuntalented/lzTxt-js (opens new window)

运行效果图

下面还是要对整体思路来个介绍的: 1.我们把整个canvas看成一屏,并且把这个屏划分成一个网格,以一个中文字符所占宽来分列,以占高来分行。还有一种情况,当一个字符的Unicode编码小于128时,它只占中文字符一半的宽度,高度不变。这时,可以得知一行最多可以放置2倍的文字。

var len_char = 1;
if(str.charCodeAt(i) > 128){//检查字符的Unicode编
	len_char = 2;
}

2.把电子书的内容分段,以换行来划分,然后一段一段绘制。 首先为每一页设置两个变量来表示页首跟页尾。这样,后面绘制时,从页尾开始向后绘制,从页头开始向前绘制。

/*本页开始*/
var page_begin = {
	line: 0,//当前行
	offset: 0//当前偏移量
};
/*本页结束*/
var page_end = {
	line: 0,//当前行
	offset: 0//当前偏移量
}

3.绘制下一页,从页尾开始,取出一段文字,逐个检查字符的Unicode编,当一行绘满,开始下一行绘制,同时要检测是否行占满,占满标示此页绘制完成。

/*开始真实绘制下一屏*/
function check(current){
	ctx.clearRect(0,0,canvas.width,canvas.height);
	
	var total = panel.col * panel.row * 2;//字符有占一位和两位的,一行最多可绘制2倍长度
	var count = 0;
	var tag = false;

	var tmp_all_write = [];
	var isBreak = false;//检查是否正常跳出
	while(current < str_write_list.length){
		
		var len = str_write_list[current].length;
		var tmp_write = [];
		var str = str_write_list[current];
		var start = 0;
		if(!tag){
			start = page_end.offset;
			tag = true;
		}
		
		var tmp = 0;
		var begin = start;
		for(var i = start; i < len ; i ++ ){
			//逐个检查
			if(tmp >= panel.col * 2 - 1){
				tmp_write.push(str.substring(begin,i));
				begin = i;
				tmp = 0;
			}
			
			var len_char = 1;
			if(str.charCodeAt(i) > 128){
				len_char = 2;
			}
			
			tmp += len_char;
		}
		
		if(tmp > 0){
			tmp_write.push(str.substring(begin,i))
		}
		
		if(str == "") {
			tmp_write.push("");
		};
		
		var offset = 0;
		if(tmp_all_write.length + tmp_write.length > panel.row) {
			for(var i = 0 ; i < tmp_all_write.length ; i ++){
				ctx.fillText(tmp_all_write[i],0, (i + 1) * font.size);
			}
			
			for(var j = 0 ; j < tmp_write.length && j + i < panel.row ; j ++){
				offset += tmp_write[j].length;
				ctx.fillText(tmp_write[j],0, (i + j + 1) * font.size);
			}
			page_end.line = current;
			page_end.offset = offset;
			isBreak = true;
			break	;
		}

		tmp_all_write = tmp_all_write.concat(tmp_write);

		current ++;
	}
	/*未正常跳出,表示到达书尾,直接绘制*/
	if(!isBreak){
		for(var i = 0 ; i < tmp_all_write.length ; i ++){
			ctx.fillText(tmp_all_write[i],0, (i + 1) * font.size);
		}
	}
	
}

3.绘制上一页,从页头开始,倒着取出一段文字,绘制一行,检测行占满。

/*上一页*/
function write_before(){
	var tag = false;
	var total = panel.col * panel.row * 2;
	var count = 0;
	var line = page_begin.offset > 0 ? page_begin.line : page_begin.line - 1 ;
	var t = 0;
	var tmp_all_write = [];
	while(line >= 0){
		
		var len = str_write_list[line].length;
		var tmp_write = [];
		var str = str_write_list[line];
		var start = len;
		if(!tag){
			if(page_begin.offset > 0 ){
				start = page_begin.offset
			}

			tag = true;
		}
		
		var tmp = 0;
		var begin = 0;
		for(var i = 0; i < start ; i ++ ){
			//逐个检查
			if(tmp >= panel.col * 2  - 1){
				tmp_write.push(str.substring(i,begin));
				begin = i;
				tmp = 0;
			}
			
			var len_char = 1;
			if(str.charCodeAt(i) > 128){
				len_char = 2;
			}
			
			tmp += len_char;
		}
		
		if(tmp > 0){
			tmp_write.push(str.substring(begin,i))
		}
		
		/*此行为空行,也单独占一行*/
		if(str == "") {
			tmp_write.push("");
		};
		
		var offset = 0;
		if(tmp_all_write.length + tmp_write.length >= panel.row) {
			ctx.clearRect(0,0,canvas.width,canvas.height);
			var y = panel.row;
			
			for(var i = tmp_all_write.length - 1 ; i >= 0  ; i --){
				ctx.fillText(tmp_all_write[i],0, (y--) * font.size);
			}
			i = tmp_all_write.length;
//						
			var k = 0;
			for(var j = tmp_write.length - 1 ; j >= 0 && (k + i) < panel.row ; j --){
				offset += tmp_write[j].length;
				k++;
				ctx.fillText(tmp_write[j],0, (y--) * font.size);
			}
			
			/*重置页尾数据*/
			page_end.line = page_begin.line;
			page_end.offset = page_begin.offset;
			/*重置页头数据*/
			page_begin.line = line;
			page_begin.offset = str.length - offset;
			break	
		}

		tmp_all_write = tmp_write.concat(tmp_all_write);

		line --;
	}
}

其实这个向前翻页的功能是多余的,可以在向后翻页的时候把当前页的信息记录下来,这样就不需要翻上一页还要计算那么多东西了。

结语:到目前为止只能算是写了个电子阅读器的demo,完成上一页下一页操作。后面需要做的还很多,比如:美化,翻页效果,文本目录结构,利用html5直接从本地读文件等等。有兴趣的在此基础上加上你想要的效果,如果你有好的作品,记得@我一观呦。