export default function Wholereader ({container, autoplay, interval, content, title, color, background, fontSize, fontFamily, split, lineGap, topGap, bottomGap, slide, pageType, backShow, headerShow, footerShow, unableClickPage, selectable }){ if(!(this instanceof Wholereader)){ //如果this不是指向MyClass throw new TypeError("TypeError: Class constructor Wholereader cannot be invoked without 'new'") } this.container = typeof container == 'string' ? document.querySelector('#' + container) : container//容器 this.content = content || ''//小说类容 this.title = title || ''//小说标题 this.color = color || '#333333'//字体颜色 this.background = background || '#fcd281'//页面背景 this.fontSize = parseInt(fontSize || 15)//字体大小 this.fontFamily = fontFamily || 'Microsoft YaHei, 微软雅黑'//字体 this.split = split || ''//分隔符 this.lineGap = parseInt(lineGap || 15)//行间隔 this.topGap = parseInt(topGap || 10)//顶部间隔 this.bottomGap = parseInt(bottomGap || 10)//底部间隔 this.slide = parseInt(slide || 20)//左右间隔 this.pageType = pageType || 'real'//翻页类型 this.backShow = backShow//显示返回按钮 this.headerShow = headerShow//显示顶部 this.footerShow = footerShow//显示底部 this.unableClickPage = unableClickPage//关闭点击翻页 this.selectable = selectable//开启文本选择 this.autoplay = autoplay || false//自动播放 this.interval = interval || 5000//自动播放周期 this._contents = []//内容集合 this._wrapperEl = null//内容盒子 this._scrollerEl = null//滚动盒子 this._viewWidth = 0//容器宽度 this._viewHeight = 0//容器高度 this._contentWidth = 0//内容宽度 this._contentHeight = 0//内容高度 this._start = 0//当前页开始位置 this._end = 0//当前页开始位置 this._pageWating = false//翻页等待 this._touchTimer = null//触摸事件定时器 this._autoplayTimer = null//自动播放定时器 this._touchTime = 0//触摸时间 this._touchstartX = 0//触摸开始点 this._moveX = 0//移动位置 this._pageEl = null //翻页对象 this._pageDirection = ''//翻页方向 this._updownloading = false//上下章节加载等待 this._eventCallback = {}//事件 } //渲染页面 Object.defineProperty(Wholereader.prototype,'render',{ value: async function(start){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader.render is not a constructor") } if ( this.container && typeof this.container != 'undefined' ) { this._stopAutoplay() this._contents = this._contentToArr()//将文本转为数组 if ( this._wrapperEl ) this.container.removeChild(this._wrapperEl)//清空容器 this._viewWidth = this.container.offsetWidth//获取容器宽度 this._viewHeight = this.container.offsetHeight//获取容器高度 this._contentWidth = this._viewWidth - (2 * this.slide)//获取内容宽度 this._contentHeight = this._viewHeight - this.topGap - this.bottomGap - (this.headerShow ? 30 : 0) - (this.footerShow ? 30 : 0)//获取内容高度 //创建内容盒子 this._wrapperEl = document.createElement('DIV') this._wrapperEl.setAttribute('class', ( this.selectable ? 'whole-reader-wrapper-selectable ' : '' ) + 'whole-reader-wrapper ' + (this.pageType == 'scroll' ? 'whole-scroll-reader' : 'whole-flip-reader')) this.container.appendChild(this._wrapperEl) this._start = start//记录当前页开始位置 const pages = []; if ( start > 0 ) pages.push(this._computedPrevText(start))//计算上一页 pages.push(this._computedNextText(start))//计算当前页 this._end = pages[pages.length - 1].end if ( pages[pages.length - 1].end < this._contents.length - 1 ) pages.push(this._computedNextText(pages[pages.length - 1].end))//计算下一页 if ( this.pageType == 'scroll' ) {//滚动阅读 this._wrapperEl.style.padding = `${this.topGap}px ${this.slide}px ${this.topGap}px ${this.slide}px`; this._wrapperEl.style.background = this.background; this._wrapperEl.style.color = this.color; if ( this.headerShow ) {//开启头部显示 const header = this._createHeaderDom() this._wrapperEl.appendChild(header) } //创建滚动元素 this._scrollerEl = document.createElement('div') this._scrollerEl.setAttribute('class', 'whole-scroll-reader-content') this._wrapperEl.appendChild(this._scrollerEl) if ( this.footerShow ) {//开启底部显示 const footer = await this._createFooterDom(this._start) this._wrapperEl.appendChild(footer) } pages.forEach(page => { this._scrollerEl.appendChild(this._createTextDom(page))//创建页面元素 }) const scrollItems = this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text') let offsetHeight = 0; for ( let i = 0; i < scrollItems.length; i++ ) { offsetHeight += i > 0 ? scrollItems[i - 1].offsetHeight : 0; if ( this._start >= scrollItems[i].getAttribute('start') && this._start < scrollItems[i].getAttribute('end') ) { this._scrollerEl.scrollTop = offsetHeight; this._end = scrollItems[i].getAttribute('end'); } } this._scrollerEl.onscroll = this._scroll.bind(this) } else {//翻页阅读 for ( let i = 0; i < pages.length; i++ ) this._wrapperEl.appendChild(await this._createPageDom(pages[i]))//创建页面元素 this._pageChange()//触发change //绑定touch事件 this._wrapperEl.ontouchstart = this._touchstart.bind(this) this._wrapperEl.ontouchmove = this._touchmove.bind(this) this._wrapperEl.ontouchend = this._touchaction.bind(this) this._wrapperEl.ontouchcancel = this._touchaction.bind(this) //兼容pc端 this._wrapperEl.onmousedown = (e) => { this._mousedown = true this._touchstart({touches: [{pageX: e.pageX, pageY: e.pageY}]}) } this._wrapperEl.onmousemove = (e) => { if ( !this._mousedown ) return this._touchmove({touches: [{pageX: e.pageX, pageY: e.pageY}]}) } this._wrapperEl.onmouseup = (e) => { this._mousedown = false this._touchaction({touches: [{pageX: e.pageX, pageY: e.pageY}]}) } } this._startAutoplay() } }, enumerable:false }) //刷新页面 Object.defineProperty(Wholereader.prototype,'refresh',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader.refresh is not a constructor") } if ( this.container && typeof this.container != 'undefined' ) { if ( this.pageType != 'scroll' && this._wrapperEl && this._scrollerEl ) {//滚动阅读改为翻页阅读时 this._wrapperEl.removeChild(this._scrollerEl) this._scrollerEl = null } this.render(this._start) } }, enumerable:false }) //设置参数 Object.defineProperty(Wholereader.prototype,'setConfig',{ value:function(attribute, value){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader.setConfig is not a constructor") } this[attribute] = value if ( attribute == 'autoplay' ) this._startAutoplay() }, enumerable:false }) //销毁 Object.defineProperty(Wholereader.prototype,'destroy',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader.destroy is not a constructor") } if ( this._wrapperEl ) { this._stopAutoplay() if ( this._touchtimer ) { window.clearTimeout(this._touchtimer) this._touchtimer = null } if ( this._scrollerEl ) { this._wrapperEl.removeChild(this._scrollerEl) this._scrollerEl = null } this.container.removeChild(this._wrapperEl) this._wrapperEl = null this._pageEl = null this._eventCallback = {} } }, enumerable:false }) // 注册事件 Object.defineProperty(Wholereader.prototype,'on',{ value:function(name, callback){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader.on is not a constructor") } this._eventCallback[name] = callback }, enumerable:false }) //往前翻页 Object.defineProperty(Wholereader.prototype,'prev',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader.prev is not a constructor") } if ( this.pageType == 'scroll' ) {//滚动阅读 const items = this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text') let index = 0 for ( let i = 0; i < items.length; i++ ) if ( items[i].getAttribute('start') == this._start ) index = i if ( index > 0 ) items[index - 1].scrollIntoView({behavior: 'smooth', 'block': 'end'})//滚动到上一个内容 else this._scrollerEl.scrollTop = 0 } else {//翻页阅读 if ( !this._pageWating ) { this._touchstartX = 1 this._pageEl = this._getPageActived(-1) this._pageDirection = 'prev' this._touchaction() } } }, enumerable:false }) //往后翻页 Object.defineProperty(Wholereader.prototype,'next',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader.next is not a constructor") } if ( this.pageType == 'scroll' ) {//滚动阅读 const items = this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text') let index = items.length - 1 for ( let i = 0; i < items.length; i++ ) if ( items[i].getAttribute('start') == this._start ) index = i if ( index < items.length - 1 ) items[index + 1].scrollIntoView({behavior: 'smooth'})//滚动到下一个内容 else this._scrollerEl.scrollTop = this._scrollerEl.scrollHeight } else {//翻页阅读 if ( !this._pageWating ) { this._touchstartX = this._viewWidth this._pageEl = this._getPageActived(0) this._pageDirection = 'next' this._touchaction() } } }, enumerable:false }) //开启自动播放 Object.defineProperty(Wholereader.prototype,'_startAutoplay',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._startAutoplay is not a constructor") } this._stopAutoplay() if ( !this.autoplay ) return if ( this.pageType == 'scroll' ) {//滚动阅读 this._autoplayTimer = window.setInterval(() => { if ( this._scrollerEl.scrollTop < this._scrollerEl.scrollHeight - this._contentHeight ) this._scrollerEl.scrollTop += 1 }, 20) } else {//翻页阅读 this._autoplayTimer = window.setTimeout(() => { let index = 0 const items = this._wrapperEl.getElementsByClassName('whole-flip-reader-page-item') for ( let i = 0; i < items.length; i++ ) if ( items[i].getAttribute('start') == this._start ) index = i if ( index < items.length - 1 ) this.next() else this._startAutoplay() }, this.interval) } }, enumerable:false }) //关闭自动播放 Object.defineProperty(Wholereader.prototype,'_stopAutoplay',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._stopAutoplay is not a constructor") } if ( !this._autoplayTimer ) return if ( this.pageType == 'scroll' ) window.clearInterval(this._autoplayTimer)//滚动阅读 else window.clearTimeout(this._autoplayTimer)//翻页阅读 this._autoplayTimer = null }, enumerable:false }) //创建一个独立的canvas画板,用于计算文字布局 Object.defineProperty(Wholereader.prototype,'_createComputedCanvas',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._createComputedCanvas is not a constructor") } const canvasDom = document.createElement('canvas'); canvasDom.width = this._contentWidth; canvasDom.height = this._contentHeight; const context = canvasDom.getContext('2d', {alpha: false}); context.font = this.fontSize + 'px ' + this.fontFamily context.imageSmoothingEnabled = false context.lineWidth = 1 return context }, enumerable:false }) //测量文字(备用) Object.defineProperty(Wholereader.prototype,'_measureText',{ value:function(text){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._measureText is not a constructor") } const span = document.createElement('SPAN'); span.style.fontSize = this.fontSize + 'px' span.style.fontFamily = this.fontFamily span.style.whiteSpace = 'pre-wrap' span.innerHTML = text document.body.appendChild(span) const width = span.offsetWidth document.body.removeChild(span) return width }, enumerable:false }) //计算当前页和下一页的文字排版 Object.defineProperty(Wholereader.prototype,'_computedNextText',{ value:function(start, end){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._computedNextText is not a constructor") } const context = this._createComputedCanvas() let pageHeight = this.fontSize + this.lineGap, text = [], length = 0, lastIndex = 0 const contentSync = end ? this._contents.slice(start, end) : this._contents.slice(start) while ( pageHeight <= this._contentHeight ) { text.push(''); let lineWidth = 0 for ( let i = lastIndex; i < contentSync.length; i++ ) { const char = contentSync[i] lineWidth += context.measureText(char).width; // lineWidth += this._measureText(char) if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) { length += 1 end = start + length; lastIndex = i + 1; break; } else if ( lineWidth >= this._contentWidth ) { lastIndex = i; break; } else { text[text.length - 1] += char length += 1; end = start + length; } } pageHeight += this.fontSize + this.lineGap if ( end >= contentSync.length - 1 + start ) break; } return {start, end, text}; }, enumerable:false }) //计算上一页的文字排版 Object.defineProperty(Wholereader.prototype,'_computedPrevText',{ value:function(end){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._computedPrevText is not a constructor") } const context = this._createComputedCanvas() let pageHeight = this.fontSize + this.lineGap, text = [], start = 0, length = 0, lastIndex1 = 0, lastIndex2 = 0 while ( pageHeight <= this._contentHeight ) { if ( end - length > 0 ) { text.unshift(''); let lineWidth = 0, contentSync = this._contents.slice(0, end) for ( let i = end - length - 1; i >= 0; i-- ) { const char = contentSync[i] lineWidth += context.measureText(char).width; // lineWidth += this._measureText(char) if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) { lastIndex1 = i - 1; length += 1 break; } else if ( lineWidth >= this._contentWidth ) { lastIndex1 = i; break; } else { text[0] = char + text[0]; length += 1 start = end - length; } if ( start == 0 ) break; } pageHeight += this.fontSize + this.lineGap } else { if ( this.pageType != 'scroll' ) { text.push(''); let lineWidth = 0, contentSync = this._contents.slice(end) for ( let i = lastIndex2; i < contentSync.length; i++ ) { const char = contentSync[i] lineWidth += context.measureText(char).width; // lineWidth += this._measureText(char) if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) { lastIndex2 = i + 1; length += 1 break; } else if ( lineWidth >= this._contentWidth ) { lastIndex2 = i; break; } else { text[text.length - 1] += char; length += 1; end = start + length; } } pageHeight += this.fontSize + this.lineGap if ( end >= this._contents.length - 1 ) break; } else break; } } return {start, end, text} }, enumerable:false }) //创建页面元素 Object.defineProperty(Wholereader.prototype,'_createPageDom',{ value:async function(page){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._createComputedCanvas is not a constructor") } //创建页面盒子 const item = document.createElement('DIV'); item.style.zIndex = this._contents.length - page.start; item.setAttribute('class', 'whole-flip-reader-page-item whole-flip-reader-page-item-start_' + page.start + (this._start == page.start ? ' whole-flip-reader-page-item-actived' : '')); item.setAttribute('start', page.start); item.setAttribute('end', page.end); //创建页面内容 const content = document.createElement('div'); content.setAttribute('class', 'whole-flip-reader-page-item-content') content.style.width = this._viewWidth + 'px'; content.style.height = this._viewHeight + 'px'; content.style.background = this.background content.style.color = this.color content.style.padding = `${this.topGap}px ${this.slide}px ${this.topGap}px ${this.slide}px`; if ( this.headerShow ) {//开启头部显示 const header = this._createHeaderDom(); content.appendChild(header); } //创建文字元素 const text = this._createTextDom(page); content.appendChild(text) if ( this.footerShow ) {//开启底部显示 const footer = await this._createFooterDom(page.start) content.appendChild(footer) } item.appendChild(content); //创建背景 const bg = document.createElement('DIV'); bg.setAttribute('class', 'whole-flip-reader-page-item-bg') bg.style.height = Math.sqrt(Math.pow(this._viewHeight, 2) + Math.pow(this._viewWidth, 2)) + 'px'; bg.style.background = this.background; item.appendChild(bg); //创建阴影区域 const shadow = document.createElement('DIV'); shadow.setAttribute('class', 'whole-flip-reader-page-item-shadow') item.appendChild(shadow); if ( page.start < this._start ) this._pageAnimation(-this._viewWidth, 0, { box: item, content, bg, shadow })//如果是上一页内容则设置已经翻页样式 return item }, enumerable:false }) //创建头部元素 Object.defineProperty(Wholereader.prototype,'_createHeaderDom',{ value:function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._createHeaderDom is not a constructor") } const header = document.createElement('DIV'); header.setAttribute('class', 'whole-reader-header') header.innerHTML = `${this.title}` if ( this.backShow ) { const back = document.createElement('DIV') back.setAttribute('class', 'whole-reader-header-back') back.style.borderTopColor = this.color back.style.borderLeftColor = this.color back.ontouchstart = function (e) { e.stopPropagation && e.stopPropagation(); } back.ontouchmove = function (e) { e.stopPropagation && e.stopPropagation(); } back.onmousedown = function (e) { e.stopPropagation && e.stopPropagation(); } back.onmousemove = function (e) { e.stopPropagation && e.stopPropagation(); } back.ontouchend = (e) => { e.stopPropagation && e.stopPropagation(); window.setTimeout(() => { this._eventCallback.back && this._eventCallback.back() }, 50)//不加延迟可能会造成返回或者跳转页面时,触发页面相同位置点击事件 } back.onmouseup = (e) => { e.stopPropagation && e.stopPropagation(); if ( !this._mousedown ) return window.setTimeout(() => { this._eventCallback.back && this._eventCallback.back() }, 50)//不加延迟可能会造成返回或者跳转页面时,触发页面相同位置点击事件 } header.insertBefore(back, header.firstChild) } // if ( this.backShow ) header.innerHTML = `
` + header.innerHTML return header }, enumerable:false }) //创建底部元素 Object.defineProperty(Wholereader.prototype,'_createFooterDom',{ value:async function(start){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._createFooterDom is not a constructor") } const progress = ((start / this._contents.length) * 100).toFixed(2) const d = new Date() const time = (d.getHours() < 10 ? ('0' + d.getHours()) : d.getHours()) + ':' + (d.getMinutes() < 10 ? ('0' + d.getMinutes()) : d.getMinutes()) const footer = document.createElement('DIV'); footer.setAttribute('class', 'whole-reader-footer') footer.innerHTML = ` ` return footer }, enumerable:false }) //创建电池元素 Object.defineProperty(Wholereader.prototype,'_createBatteryDom',{ value: async function(){ if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用 throw new TypeError("TypeError: Wholereader._createBatteryDom is not a constructor") } const max = 16 const res = window.navigator.getBattery ? await window.navigator.getBattery() : {level: 1} const value = res.level * max return `