|  | <html> | 
						
						
							|  | 	<head> | 
						
						
							|  | 		<meta | 
						
						
							|  | 		  name="viewport" | 
						
						
							|  | 		  content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" | 
						
						
							|  | 		/> | 
						
						
							|  | 		<title>计算文本</title> | 
						
						
							|  | 		<style type="text/css"> | 
						
						
							|  | 			html,body { | 
						
						
							|  | 				margin: 0; | 
						
						
							|  | 				padding: 0; | 
						
						
							|  | 				width: 100%; | 
						
						
							|  | 				height: 100%; | 
						
						
							|  | 				overflow: hidden; | 
						
						
							|  | 				box-sizing: border-box; | 
						
						
							|  | 			} | 
						
						
							|  | 			.yingbing-computed-wrapper { | 
						
						
							|  | 				width: 100%; | 
						
						
							|  | 				height: 100%; | 
						
						
							|  | 				box-sizing: border-box; | 
						
						
							|  | 			} | 
						
						
							|  | 			.yingbing-img { | 
						
						
							|  | 				max-width: 100%!important; | 
						
						
							|  | 				max-height: 100%!important; | 
						
						
							|  | 			} | 
						
						
							|  | 		</style> | 
						
						
							|  | 	</head> | 
						
						
							|  | 	<body> | 
						
						
							|  | 		<div class="yingbing-computed-wrapper"></div> | 
						
						
							|  | 	</body> | 
						
						
							|  | 	<script type="text/javascript" src="./js/uni-webview-js@1.5.4.js"></script> | 
						
						
							|  | 	<script type="text/javascript"> | 
						
						
							|  | 		var htmlChars = ['㊦', '㊟', '㊨', '㊧'], | 
						
						
							|  | 		windowHeight = 0,//窗口高度 | 
						
						
							|  | 		pages = [],//渲染页面数组 | 
						
						
							|  | 		chapter = {},//章节内容临时存储 | 
						
						
							|  | 		contents = [],//内容转化数组 | 
						
						
							|  | 		startTags = [],//临时存储被切割的开始标签 | 
						
						
							|  | 		handleTimer = null//定时处理器 | 
						
						
							|  | 		fontSize = 0, | 
						
						
							|  | 		fontFamily = '', | 
						
						
							|  | 		lineHeight = 0, | 
						
						
							|  | 		totalChapter = 0 | 
						
						
							|  | 		//外层包裹div,设置公共样式(为了兼容微信小程序) | 
						
						
							|  | 		function getContent (content) { | 
						
						
							|  | 			return `<div class="yingbing-computed-content-html" style="font-family:${fontFamily};font-size:${fontSize}px;line-height:${lineHeight}px;">` + content.replace('<whole-render', '<div').replace('<\/whole-render>', '<\/div>') + '</div>' | 
						
						
							|  | 		} | 
						
						
							|  | 		function start (c, s) { | 
						
						
							|  | 			destroyHandleTimer() | 
						
						
							|  | 			chapter = c//记录章节内容 | 
						
						
							|  | 			fontSize = s.fontSize | 
						
						
							|  | 			fontFamily = s.fontFamily | 
						
						
							|  | 			lineHeight = s.lineHeight | 
						
						
							|  | 			totalChapter = s.totalChapter | 
						
						
							|  | 			windowHeight = document.body.offsetHeight; | 
						
						
							|  | 			if ( !chapter.content ) { | 
						
						
							|  | 				handleSuccess() | 
						
						
							|  | 				return | 
						
						
							|  | 			} | 
						
						
							|  | 			let content = chapter.content | 
						
						
							|  | 			const fullStyle = 'box-sizing:border-box;overflow:hidden;height:' + windowHeight + 'px'//full-box的样式 | 
						
						
							|  | 			content = content.replace(/<full-box.*?>/g, function(item){ | 
						
						
							|  | 			    if(item.includes('style=')){ | 
						
						
							|  | 			        return item.replace(/style="(.*?)(?=\")/, 'style="' + fullStyle +';$1'); | 
						
						
							|  | 			    }else{ | 
						
						
							|  | 			        return item.replace(/\<full-box/, '<full-box style="'+fullStyle+'" $1'); | 
						
						
							|  | 			    } | 
						
						
							|  | 			})//给所有full-box添加全部高度 | 
						
						
							|  | 			content = content.replace(/<full-box/g, '<whole-render')//将full-box标签替换为whole-render | 
						
						
							|  | 			content = content.replace(/<\/full-box>/g, '</whole-render>')//将full-box标签替换为whole-render | 
						
						
							|  | 			const imgClassName = 'yingbing-img'//img类名 | 
						
						
							|  | 			content = content.replace(/<img.*?>/g, function(item){ | 
						
						
							|  | 			    if(item.includes('class=')){ | 
						
						
							|  | 			        return item.replace(/class="(.*?)(?=\")/, 'class="$1 '+imgClassName); | 
						
						
							|  | 			    }else{ | 
						
						
							|  | 			        return item.replace(/\<img (.*?)(?=\")/, '<img class="'+imgClassName+'" $1'); | 
						
						
							|  | 			    } | 
						
						
							|  | 			})//给所有img添加class[yingbing-img] 设置img公共样式(为了兼容微信小程序) | 
						
						
							|  | 			content = content.replace(/\r|\n/g, '<br/>')//将\n|\r替换为换行符 | 
						
						
							|  | 			content = content.replace(/\t/g, '')//将\t删除 | 
						
						
							|  | 			contents = contentToArr(content)//初始化内容,并将文本内容转为数组 | 
						
						
							|  | 			computedPage() | 
						
						
							|  | 		} | 
						
						
							|  | 		//将文本内容转为数组 | 
						
						
							|  | 		function contentToArr (content) { | 
						
						
							|  | 			let temp = null | 
						
						
							|  | 			let originContent = content | 
						
						
							|  | 			const wholeRegTags= ['whole-render', 'full-box', 'button', 'video', 'audio', 'iframe']//需要整块渲染的标签 | 
						
						
							|  | 			const singleRegTags = ['img', 'hr', 'input', 'video', 'audio', 'iframe']//非闭合标签 | 
						
						
							|  | 			const wrapTags = [], wholeTags = [], endTags = [], startTags = [] | 
						
						
							|  | 			//将整块渲染的标签替换为㊟符号保存 | 
						
						
							|  | 			const wholeTagReg = new RegExp(`<(${wholeRegTags.join('|')})>*([\\s\\S]*?)<\/(${wholeRegTags.join('|')})>|<(${singleRegTags.join('|')})[^>]+>`) | 
						
						
							|  | 			while ( (temp = originContent.match(wholeTagReg)) != null ) { | 
						
						
							|  | 				wholeTags.push(temp[0]) | 
						
						
							|  | 				originContent = originContent.substring(0, temp.index) + htmlChars[1] + originContent.substring(temp.index + temp[0].length) | 
						
						
							|  | 			} | 
						
						
							|  | 			//将换行标签替换为㊦符号保存 | 
						
						
							|  | 			const wrapTagReg = new RegExp(`<br[^>]+>`) | 
						
						
							|  | 			while ( (temp = originContent.match(wrapTagReg)) != null ) { | 
						
						
							|  | 				wrapTags.push(temp[0]) | 
						
						
							|  | 				originContent = originContent.substring(0, temp.index) + htmlChars[0] + originContent.substring(temp.index + temp[0].length) | 
						
						
							|  | 			} | 
						
						
							|  | 			//将结束标签替换为㊨符号保存 | 
						
						
							|  | 			const endTagReg = new RegExp(`<\/[^>]+>`)//结束标签 | 
						
						
							|  | 			while ( (temp = originContent.match(endTagReg)) != null ) { | 
						
						
							|  | 				endTags.push(temp[0]) | 
						
						
							|  | 				originContent = originContent.substring(0, temp.index) + htmlChars[2] + originContent.substring(temp.index + temp[0].length) | 
						
						
							|  | 			} | 
						
						
							|  | 			//将开始标签替换为㊧符号保存 | 
						
						
							|  | 			const startTagReg = new RegExp(`<[^>]+>`)//开始标签 | 
						
						
							|  | 			while ( (temp = originContent.match(startTagReg)) != null ) { | 
						
						
							|  | 				startTags.push(temp[0]) | 
						
						
							|  | 				originContent = originContent.substring(0, temp.index) + htmlChars[3] + originContent.substring(temp.index + temp[0].length) | 
						
						
							|  | 			} | 
						
						
							|  | 			//将字符内容分割为数组 | 
						
						
							|  | 			const arr = originContent.split('') | 
						
						
							|  | 			return arr.map((char, key) => { | 
						
						
							|  | 				if ( htmlChars.indexOf(char) == -1 ) return char//非标签内容直接返回 | 
						
						
							|  | 				else {//标签内容转化为真实标签再返回 | 
						
						
							|  | 					let index = -1 | 
						
						
							|  | 					for ( let j = 0; j < key + 1; j++ ) if ( originContent.charAt(j) == char ) index++ | 
						
						
							|  | 					const tags = char == '㊦' ? wrapTags : char == '㊟' ? wholeTags : char == '㊨' ? endTags : startTags | 
						
						
							|  | 					return index > -1 ? char + ':' + tags[index] : char | 
						
						
							|  | 				} | 
						
						
							|  | 			}) | 
						
						
							|  | 		} | 
						
						
							|  | 		//计算页面 | 
						
						
							|  | 		function  computedPage () { | 
						
						
							|  | 			const start = pages.length > 0 ? pages[pages.length-1].end : 0//获取字符开始位置 | 
						
						
							|  | 			//新增页面 | 
						
						
							|  | 			pages.push( Object.assign({}, chapter, { | 
						
						
							|  | 				content: '', | 
						
						
							|  | 				contents: [], | 
						
						
							|  | 				type: 'text', | 
						
						
							|  | 				start: start, | 
						
						
							|  | 				end: 0, | 
						
						
							|  | 			}) ) | 
						
						
							|  | 			const div = document.createElement('DIV') | 
						
						
							|  | 			div.setAttribute('class', 'yingbing-page-' + pages.length) | 
						
						
							|  | 			document.querySelector('.yingbing-computed-wrapper').appendChild(div) | 
						
						
							|  | 			//给页面添加内容 | 
						
						
							|  | 			addText(start) | 
						
						
							|  | 		} | 
						
						
							|  | 		//增加内容 | 
						
						
							|  | 		function addText (start) { | 
						
						
							|  | 			let text = '',//渲染内容临时存储 | 
						
						
							|  | 			end = 0,//结束位置 | 
						
						
							|  | 			lt = startTags.length,//开始标签数量 | 
						
						
							|  | 			rt = 0//结束标签数量 | 
						
						
							|  | 			if ( startTags.length > 0 ) text += startTags.map(t => t.split('+')[1]).join('')//如果存在被切割的开始标签添加到内容最前面 | 
						
						
							|  | 			//截取字符 渲染 | 
						
						
							|  | 			for ( let i = start; i < contents.length; i++ ) { | 
						
						
							|  | 				end = i//记录结束位置 | 
						
						
							|  | 				const char = contents[i] | 
						
						
							|  | 				text += char//拼接文本 | 
						
						
							|  | 				if ( htmlChars.findIndex(c => char.indexOf(c)) > -1 ) {//如果是标签内容 | 
						
						
							|  | 					if ( char.indexOf(htmlChars[0] + ':') > -1 && lt == rt ) break;//如是换行标签并且开始标签和结束标签数量一致时直接开始渲染 | 
						
						
							|  | 					if ( char.indexOf(htmlChars[3] + ':') > -1 ) lt++//开始标签+1 | 
						
						
							|  | 					if ( char.indexOf(htmlChars[2] + ':') > -1 ) rt++//结束标签+1 | 
						
						
							|  | 					if ( lt == rt ) break;//如果开始标签数量等于结束标签则直接渲染 | 
						
						
							|  | 				} | 
						
						
							|  | 			} | 
						
						
							|  | 			startTags = [] | 
						
						
							|  | 			pages[pages.length-1].content += text.replace(new RegExp(`(${htmlChars.join('|')}):`, 'g'), '')// 开始渲染内容 | 
						
						
							|  | 			pages[pages.length-1].end = end//记录结束位置 | 
						
						
							|  | 			const handle = () => { | 
						
						
							|  | 				destroyHandleTimer() | 
						
						
							|  | 				if ( height <= windowHeight ) {//如果内容高度小于或等于窗口高度 | 
						
						
							|  | 					if ( end < contents.length - 1 ) {//如果内容还没有渲染完 | 
						
						
							|  | 						const subheight = windowHeight - height//页面剩余高度 | 
						
						
							|  | 						if ( subheight > lineHeight ) addText(end+1)//当前剩余高度大于行高,则继续增加内容 | 
						
						
							|  | 						else { | 
						
						
							|  | 							pages[pages.length-1].end = end + 1//如果是增加内容时完成本页计算,结束位置要加1 | 
						
						
							|  | 							computedPage()//否则开始计算下一页 | 
						
						
							|  | 						} | 
						
						
							|  | 					} else { | 
						
						
							|  | 						pages[pages.length-1].end = end + 1//如果是增加内容时完成章节计算,结束位置要加1 | 
						
						
							|  | 						handleSuccess()//页面计算完毕开始返回 | 
						
						
							|  | 					} | 
						
						
							|  | 				} else { | 
						
						
							|  | 					//如果内容高度大于窗口高度开始减少内容 | 
						
						
							|  | 					subText() | 
						
						
							|  | 				} | 
						
						
							|  | 			} | 
						
						
							|  | 			let height = 0 | 
						
						
							|  | 			const div = document.querySelector('.yingbing-page-' + pages.length) | 
						
						
							|  | 			div.innerHTML = getContent(pages[pages.length-1].content) | 
						
						
							|  | 			if ( /<(img|video|audio|iframe)/.test(text)) { | 
						
						
							|  | 				handleTimer = window.setTimeout(() => { | 
						
						
							|  | 					height = div.offsetHeight | 
						
						
							|  | 					handle() | 
						
						
							|  | 				}, 1000) | 
						
						
							|  | 			} else { | 
						
						
							|  | 				height = div.offsetHeight | 
						
						
							|  | 				handle() | 
						
						
							|  | 			} | 
						
						
							|  | 		} | 
						
						
							|  | 		//减少内容 | 
						
						
							|  | 		function subText () { | 
						
						
							|  | 			const page = pages[pages.length - 1]//获取最后一页渲染数据 | 
						
						
							|  | 			let contents1 = contentToArr(page.content),//将当前页内容转化为数组 | 
						
						
							|  | 			end = page.end,//结束位置减1 | 
						
						
							|  | 			index = 0,//字符索引 | 
						
						
							|  | 			endTags = []//结束字符临时存储 | 
						
						
							|  | 			contents1.reverse()//反转文本内容集合 | 
						
						
							|  | 			const handle = () => { | 
						
						
							|  | 				let lts = [],//存储开始标签 | 
						
						
							|  | 				rts = []//存储结束标签 | 
						
						
							|  | 				end--//结束位置减1 | 
						
						
							|  | 				const char = contents1[index]//获取文本内容最后一位 | 
						
						
							|  | 				if ( char.indexOf(htmlChars[2] + ':') > -1 ) {//如果最后一位是结束标签 | 
						
						
							|  | 					let startIndex = 0//存储结束标签对应开始标签的位置 | 
						
						
							|  | 					for ( let i = index; i < contents1.length; i++ ) {//循环字符集合,找到结束标签对应的开始标签 | 
						
						
							|  | 						const char1 = contents1[i] | 
						
						
							|  | 						if ( char1.indexOf(htmlChars[2] + ':') > -1 ) rts.unshift(char1)//如果标签是结束标签加入rts集合 | 
						
						
							|  | 						if ( char1.indexOf(htmlChars[3] + ':') > -1 ) lts.push(char1)//如果标签是开始标签加入lts集合 | 
						
						
							|  | 						if ( lts.length == rts.length ) break//如果开始标签数量等于结束标签时中段循环 | 
						
						
							|  | 						startIndex = i | 
						
						
							|  | 					} | 
						
						
							|  | 					if ( lts.length > 0 ) startTags.push(startIndex + '+' + lts[lts.length-1])//将结束最后一位结束标签对应的开始标签加入startTags | 
						
						
							|  | 					endTags.unshift(startIndex + '+' + char)//存储结束标签,用于最后的拼接 | 
						
						
							|  | 				} | 
						
						
							|  | 				if ( char.indexOf(htmlChars[3] + ':') > -1 ) {//如果最后一位是开始标签 | 
						
						
							|  | 					const startIndex = startTags.findIndex(t => t.indexOf(index + '+') > -1)//获取当前开始标签在startTags中的位置 | 
						
						
							|  | 					const endIndex = endTags.findIndex(t => t.indexOf(index + '+') > -1)//获取当前开始标签在endTags中对应的结束标签位置 | 
						
						
							|  | 					startTags.splice(startIndex, 1)//删除记录的开始标签 | 
						
						
							|  | 					endTags.splice(endIndex, 1)//删除记录的当前开始标签对应的结束标签 | 
						
						
							|  | 				} | 
						
						
							|  | 				let contents2 = JSON.parse(JSON.stringify(contents1.slice(index)))//获取文本集合,并从索引位置开始截取 | 
						
						
							|  | 				contents2 = contents2.slice(1)//去掉最后一位 | 
						
						
							|  | 				contents2.reverse()//反转文本 | 
						
						
							|  | 				const text = contents2.join('') + endTags.map(t => t.split('+')[1]).join('')//拼接结束标签 | 
						
						
							|  | 				pages[pages.length - 1].content = text.replace(new RegExp(`(${htmlChars.join('|')}):`, 'g'), '')//渲染文本 | 
						
						
							|  | 				pages[pages.length - 1].end = end + 1//记录结束位置 | 
						
						
							|  | 				const div = document.querySelector('.yingbing-page-' + pages.length) | 
						
						
							|  | 				div.innerHTML = getContent(pages[pages.length-1].content) | 
						
						
							|  | 				const height = div.offsetHeight | 
						
						
							|  | 				if ( height > windowHeight ) {//如果内容高度大于窗口高度 | 
						
						
							|  | 					index++//索引+1 | 
						
						
							|  | 					handle()//继续减少文本内容 | 
						
						
							|  | 				} else { | 
						
						
							|  | 					if ( end < contents.length - 1 ) computedPage()//如果内容还没有渲染完毕,继续计算下一页 | 
						
						
							|  | 					else handleSuccess()//页面计算完毕开始返回 | 
						
						
							|  | 				} | 
						
						
							|  | 			} | 
						
						
							|  | 			handle() | 
						
						
							|  | 		} | 
						
						
							|  | 		//成功回调 | 
						
						
							|  | 		function handleSuccess () { | 
						
						
							|  | 			const slots1 = chapter.frontSlots || []//获取章节前置插槽 | 
						
						
							|  | 			const slots2 = chapter.backSlots || []//获取章节后置插槽 | 
						
						
							|  | 			slots1.reverse()//反转前置插槽 | 
						
						
							|  | 			//插入前置插槽 | 
						
						
							|  | 			slots1.forEach(name => { | 
						
						
							|  | 				const start = pages.length > 0 ? pages[0].start : 2 | 
						
						
							|  | 				pages.unshift(Object.assign({}, chapter, { | 
						
						
							|  | 					type: 'slot', | 
						
						
							|  | 					content: name + ':' + chapter.index, | 
						
						
							|  | 					start: start - 2, | 
						
						
							|  | 					end: start - 1, | 
						
						
							|  | 				})) | 
						
						
							|  | 			}) | 
						
						
							|  | 			//插入后置插槽 | 
						
						
							|  | 			slots2.forEach(name => { | 
						
						
							|  | 				const end = pages.length > 0 ? pages[pages.length - 1].end : -1 | 
						
						
							|  | 				pages.push(Object.assign({}, chapter, { | 
						
						
							|  | 					type: 'slot', | 
						
						
							|  | 					content: name + ':' + chapter.index, | 
						
						
							|  | 					start: end + 1, | 
						
						
							|  | 					end: end + 2, | 
						
						
							|  | 				})) | 
						
						
							|  | 			}) | 
						
						
							|  | 			pages = pages.map((p, key) => { | 
						
						
							|  | 				const total = pages.length | 
						
						
							|  | 				const current = key + 1 | 
						
						
							|  | 				const rate = 1 / totalChapter | 
						
						
							|  | 				const progress = totalChapter ? (rate * (current / total)) + ((p.index - 1) * rate) : 0 | 
						
						
							|  | 				return Object.assign({}, p, {total: total, current: current, progress: progress * 100}, p.type == 'text' ? {contents: contents.slice(p.start, p.end).map(char => char.replace(new RegExp(`(${htmlChars.join('|')}):`), ''))} : {}) | 
						
						
							|  | 			}) | 
						
						
							|  | 			triggerMethod('handleSuccess', pages) | 
						
						
							|  | 			document.querySelector('.yingbing-computed-wrapper').innerHTML = '' | 
						
						
							|  | 			pages = [] | 
						
						
							|  | 			startTags = [] | 
						
						
							|  | 			contents = [] | 
						
						
							|  | 			chapter = {} | 
						
						
							|  | 		} | 
						
						
							|  | 		//销毁处理定时任务 | 
						
						
							|  | 		function destroyHandleTimer () { | 
						
						
							|  | 			if ( handleTimer ) { | 
						
						
							|  | 				window.clearTimeout(handleTimer) | 
						
						
							|  | 				handleTimer = null | 
						
						
							|  | 			} | 
						
						
							|  | 		} | 
						
						
							|  | 		function triggerMethod (name, args) { | 
						
						
							|  | 			uni.postMessage({ | 
						
						
							|  | 				data: { | 
						
						
							|  | 					[name]: args | 
						
						
							|  | 				} | 
						
						
							|  | 			}); | 
						
						
							|  | 		} | 
						
						
							|  | 	</script> | 
						
						
							|  | </html> |