四零语境前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

866 lines
38 KiB

  1. export default function Wholereader ({container, autoplay, interval, content, title, color, background, fontSize, fontFamily, split, lineGap, topGap, bottomGap, slide, pageType, backShow, headerShow, footerShow, unableClickPage, selectable }){
  2. if(!(this instanceof Wholereader)){ //如果this不是指向MyClass
  3. throw new TypeError("TypeError: Class constructor Wholereader cannot be invoked without 'new'")
  4. }
  5. this.container = typeof container == 'string' ? document.querySelector('#' + container) : container//容器
  6. this.content = content || ''//小说类容
  7. this.title = title || ''//小说标题
  8. this.color = color || '#333333'//字体颜色
  9. this.background = background || '#fcd281'//页面背景
  10. this.fontSize = parseInt(fontSize || 15)//字体大小
  11. this.fontFamily = fontFamily || 'Microsoft YaHei, 微软雅黑'//字体
  12. this.split = split || ''//分隔符
  13. this.lineGap = parseInt(lineGap || 15)//行间隔
  14. this.topGap = parseInt(topGap || 10)//顶部间隔
  15. this.bottomGap = parseInt(bottomGap || 10)//底部间隔
  16. this.slide = parseInt(slide || 20)//左右间隔
  17. this.pageType = pageType || 'real'//翻页类型
  18. this.backShow = backShow//显示返回按钮
  19. this.headerShow = headerShow//显示顶部
  20. this.footerShow = footerShow//显示底部
  21. this.unableClickPage = unableClickPage//关闭点击翻页
  22. this.selectable = selectable//开启文本选择
  23. this.autoplay = autoplay || false//自动播放
  24. this.interval = interval || 5000//自动播放周期
  25. this._contents = []//内容集合
  26. this._wrapperEl = null//内容盒子
  27. this._scrollerEl = null//滚动盒子
  28. this._viewWidth = 0//容器宽度
  29. this._viewHeight = 0//容器高度
  30. this._contentWidth = 0//内容宽度
  31. this._contentHeight = 0//内容高度
  32. this._start = 0//当前页开始位置
  33. this._end = 0//当前页开始位置
  34. this._pageWating = false//翻页等待
  35. this._touchTimer = null//触摸事件定时器
  36. this._autoplayTimer = null//自动播放定时器
  37. this._touchTime = 0//触摸时间
  38. this._touchstartX = 0//触摸开始点
  39. this._moveX = 0//移动位置
  40. this._pageEl = null //翻页对象
  41. this._pageDirection = ''//翻页方向
  42. this._updownloading = false//上下章节加载等待
  43. this._eventCallback = {}//事件
  44. }
  45. //渲染页面
  46. Object.defineProperty(Wholereader.prototype,'render',{
  47. value: async function(start){
  48. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  49. throw new TypeError("TypeError: Wholereader.render is not a constructor")
  50. }
  51. if ( this.container && typeof this.container != 'undefined' ) {
  52. this._stopAutoplay()
  53. this._contents = this._contentToArr()//将文本转为数组
  54. if ( this._wrapperEl ) this.container.removeChild(this._wrapperEl)//清空容器
  55. this._viewWidth = this.container.offsetWidth//获取容器宽度
  56. this._viewHeight = this.container.offsetHeight//获取容器高度
  57. this._contentWidth = this._viewWidth - (2 * this.slide)//获取内容宽度
  58. this._contentHeight = this._viewHeight - this.topGap - this.bottomGap - (this.headerShow ? 30 : 0) - (this.footerShow ? 30 : 0)//获取内容高度
  59. //创建内容盒子
  60. this._wrapperEl = document.createElement('DIV')
  61. this._wrapperEl.setAttribute('class', ( this.selectable ? 'whole-reader-wrapper-selectable ' : '' ) + 'whole-reader-wrapper ' + (this.pageType == 'scroll' ? 'whole-scroll-reader' : 'whole-flip-reader'))
  62. this.container.appendChild(this._wrapperEl)
  63. this._start = start//记录当前页开始位置
  64. const pages = [];
  65. if ( start > 0 ) pages.push(this._computedPrevText(start))//计算上一页
  66. pages.push(this._computedNextText(start))//计算当前页
  67. this._end = pages[pages.length - 1].end
  68. if ( pages[pages.length - 1].end < this._contents.length - 1 ) pages.push(this._computedNextText(pages[pages.length - 1].end))//计算下一页
  69. if ( this.pageType == 'scroll' ) {//滚动阅读
  70. this._wrapperEl.style.padding = `${this.topGap}px ${this.slide}px ${this.topGap}px ${this.slide}px`;
  71. this._wrapperEl.style.background = this.background;
  72. this._wrapperEl.style.color = this.color;
  73. if ( this.headerShow ) {//开启头部显示
  74. const header = this._createHeaderDom()
  75. this._wrapperEl.appendChild(header)
  76. }
  77. //创建滚动元素
  78. this._scrollerEl = document.createElement('div')
  79. this._scrollerEl.setAttribute('class', 'whole-scroll-reader-content')
  80. this._wrapperEl.appendChild(this._scrollerEl)
  81. if ( this.footerShow ) {//开启底部显示
  82. const footer = await this._createFooterDom(this._start)
  83. this._wrapperEl.appendChild(footer)
  84. }
  85. pages.forEach(page => {
  86. this._scrollerEl.appendChild(this._createTextDom(page))//创建页面元素
  87. })
  88. const scrollItems = this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text')
  89. let offsetHeight = 0;
  90. for ( let i = 0; i < scrollItems.length; i++ ) {
  91. offsetHeight += i > 0 ? scrollItems[i - 1].offsetHeight : 0;
  92. if ( this._start >= scrollItems[i].getAttribute('start') && this._start < scrollItems[i].getAttribute('end') ) {
  93. this._scrollerEl.scrollTop = offsetHeight;
  94. this._end = scrollItems[i].getAttribute('end');
  95. }
  96. }
  97. this._scrollerEl.onscroll = this._scroll.bind(this)
  98. } else {//翻页阅读
  99. for ( let i = 0; i < pages.length; i++ ) this._wrapperEl.appendChild(await this._createPageDom(pages[i]))//创建页面元素
  100. this._pageChange()//触发change
  101. //绑定touch事件
  102. this._wrapperEl.ontouchstart = this._touchstart.bind(this)
  103. this._wrapperEl.ontouchmove = this._touchmove.bind(this)
  104. this._wrapperEl.ontouchend = this._touchaction.bind(this)
  105. this._wrapperEl.ontouchcancel = this._touchaction.bind(this)
  106. //兼容pc端
  107. this._wrapperEl.onmousedown = (e) => {
  108. this._mousedown = true
  109. this._touchstart({touches: [{pageX: e.pageX, pageY: e.pageY}]})
  110. }
  111. this._wrapperEl.onmousemove = (e) => {
  112. if ( !this._mousedown ) return
  113. this._touchmove({touches: [{pageX: e.pageX, pageY: e.pageY}]})
  114. }
  115. this._wrapperEl.onmouseup = (e) => {
  116. this._mousedown = false
  117. this._touchaction({touches: [{pageX: e.pageX, pageY: e.pageY}]})
  118. }
  119. }
  120. this._startAutoplay()
  121. }
  122. },
  123. enumerable:false
  124. })
  125. //刷新页面
  126. Object.defineProperty(Wholereader.prototype,'refresh',{
  127. value:function(){
  128. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  129. throw new TypeError("TypeError: Wholereader.refresh is not a constructor")
  130. }
  131. if ( this.container && typeof this.container != 'undefined' ) {
  132. if ( this.pageType != 'scroll' && this._wrapperEl && this._scrollerEl ) {//滚动阅读改为翻页阅读时
  133. this._wrapperEl.removeChild(this._scrollerEl)
  134. this._scrollerEl = null
  135. }
  136. this.render(this._start)
  137. }
  138. },
  139. enumerable:false
  140. })
  141. //设置参数
  142. Object.defineProperty(Wholereader.prototype,'setConfig',{
  143. value:function(attribute, value){
  144. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  145. throw new TypeError("TypeError: Wholereader.setConfig is not a constructor")
  146. }
  147. this[attribute] = value
  148. if ( attribute == 'autoplay' ) this._startAutoplay()
  149. },
  150. enumerable:false
  151. })
  152. //销毁
  153. Object.defineProperty(Wholereader.prototype,'destroy',{
  154. value:function(){
  155. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  156. throw new TypeError("TypeError: Wholereader.destroy is not a constructor")
  157. }
  158. if ( this._wrapperEl ) {
  159. this._stopAutoplay()
  160. if ( this._touchtimer ) {
  161. window.clearTimeout(this._touchtimer)
  162. this._touchtimer = null
  163. }
  164. if ( this._scrollerEl ) {
  165. this._wrapperEl.removeChild(this._scrollerEl)
  166. this._scrollerEl = null
  167. }
  168. this.container.removeChild(this._wrapperEl)
  169. this._wrapperEl = null
  170. this._pageEl = null
  171. this._eventCallback = {}
  172. }
  173. },
  174. enumerable:false
  175. })
  176. // 注册事件
  177. Object.defineProperty(Wholereader.prototype,'on',{
  178. value:function(name, callback){
  179. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  180. throw new TypeError("TypeError: Wholereader.on is not a constructor")
  181. }
  182. this._eventCallback[name] = callback
  183. },
  184. enumerable:false
  185. })
  186. //往前翻页
  187. Object.defineProperty(Wholereader.prototype,'prev',{
  188. value:function(){
  189. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  190. throw new TypeError("TypeError: Wholereader.prev is not a constructor")
  191. }
  192. if ( this.pageType == 'scroll' ) {//滚动阅读
  193. const items = this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text')
  194. let index = 0
  195. for ( let i = 0; i < items.length; i++ ) if ( items[i].getAttribute('start') == this._start ) index = i
  196. if ( index > 0 ) items[index - 1].scrollIntoView({behavior: 'smooth', 'block': 'end'})//滚动到上一个内容
  197. else this._scrollerEl.scrollTop = 0
  198. } else {//翻页阅读
  199. if ( !this._pageWating ) {
  200. this._touchstartX = 1
  201. this._pageEl = this._getPageActived(-1)
  202. this._pageDirection = 'prev'
  203. this._touchaction()
  204. }
  205. }
  206. },
  207. enumerable:false
  208. })
  209. //往后翻页
  210. Object.defineProperty(Wholereader.prototype,'next',{
  211. value:function(){
  212. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  213. throw new TypeError("TypeError: Wholereader.next is not a constructor")
  214. }
  215. if ( this.pageType == 'scroll' ) {//滚动阅读
  216. const items = this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text')
  217. let index = items.length - 1
  218. for ( let i = 0; i < items.length; i++ ) if ( items[i].getAttribute('start') == this._start ) index = i
  219. if ( index < items.length - 1 ) items[index + 1].scrollIntoView({behavior: 'smooth'})//滚动到下一个内容
  220. else this._scrollerEl.scrollTop = this._scrollerEl.scrollHeight
  221. } else {//翻页阅读
  222. if ( !this._pageWating ) {
  223. this._touchstartX = this._viewWidth
  224. this._pageEl = this._getPageActived(0)
  225. this._pageDirection = 'next'
  226. this._touchaction()
  227. }
  228. }
  229. },
  230. enumerable:false
  231. })
  232. //开启自动播放
  233. Object.defineProperty(Wholereader.prototype,'_startAutoplay',{
  234. value:function(){
  235. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  236. throw new TypeError("TypeError: Wholereader._startAutoplay is not a constructor")
  237. }
  238. this._stopAutoplay()
  239. if ( !this.autoplay ) return
  240. if ( this.pageType == 'scroll' ) {//滚动阅读
  241. this._autoplayTimer = window.setInterval(() => {
  242. if ( this._scrollerEl.scrollTop < this._scrollerEl.scrollHeight - this._contentHeight ) this._scrollerEl.scrollTop += 1
  243. }, 20)
  244. } else {//翻页阅读
  245. this._autoplayTimer = window.setTimeout(() => {
  246. let index = 0
  247. const items = this._wrapperEl.getElementsByClassName('whole-flip-reader-page-item')
  248. for ( let i = 0; i < items.length; i++ ) if ( items[i].getAttribute('start') == this._start ) index = i
  249. if ( index < items.length - 1 ) this.next()
  250. else this._startAutoplay()
  251. }, this.interval)
  252. }
  253. },
  254. enumerable:false
  255. })
  256. //关闭自动播放
  257. Object.defineProperty(Wholereader.prototype,'_stopAutoplay',{
  258. value:function(){
  259. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  260. throw new TypeError("TypeError: Wholereader._stopAutoplay is not a constructor")
  261. }
  262. if ( !this._autoplayTimer ) return
  263. if ( this.pageType == 'scroll' ) window.clearInterval(this._autoplayTimer)//滚动阅读
  264. else window.clearTimeout(this._autoplayTimer)//翻页阅读
  265. this._autoplayTimer = null
  266. },
  267. enumerable:false
  268. })
  269. //创建一个独立的canvas画板,用于计算文字布局
  270. Object.defineProperty(Wholereader.prototype,'_createComputedCanvas',{
  271. value:function(){
  272. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  273. throw new TypeError("TypeError: Wholereader._createComputedCanvas is not a constructor")
  274. }
  275. const canvasDom = document.createElement('canvas');
  276. canvasDom.width = this._contentWidth;
  277. canvasDom.height = this._contentHeight;
  278. const context = canvasDom.getContext('2d', {alpha: false});
  279. context.font = this.fontSize + 'px ' + this.fontFamily
  280. context.imageSmoothingEnabled = false
  281. context.lineWidth = 1
  282. return context
  283. },
  284. enumerable:false
  285. })
  286. //测量文字(备用)
  287. Object.defineProperty(Wholereader.prototype,'_measureText',{
  288. value:function(text){
  289. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  290. throw new TypeError("TypeError: Wholereader._measureText is not a constructor")
  291. }
  292. const span = document.createElement('SPAN');
  293. span.style.fontSize = this.fontSize + 'px'
  294. span.style.fontFamily = this.fontFamily
  295. span.style.whiteSpace = 'pre-wrap'
  296. span.innerHTML = text
  297. document.body.appendChild(span)
  298. const width = span.offsetWidth
  299. document.body.removeChild(span)
  300. return width
  301. },
  302. enumerable:false
  303. })
  304. //计算当前页和下一页的文字排版
  305. Object.defineProperty(Wholereader.prototype,'_computedNextText',{
  306. value:function(start, end){
  307. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  308. throw new TypeError("TypeError: Wholereader._computedNextText is not a constructor")
  309. }
  310. const context = this._createComputedCanvas()
  311. let pageHeight = this.fontSize + this.lineGap, text = [], length = 0, lastIndex = 0
  312. const contentSync = end ? this._contents.slice(start, end) : this._contents.slice(start)
  313. while ( pageHeight <= this._contentHeight ) {
  314. text.push('');
  315. let lineWidth = 0
  316. for ( let i = lastIndex; i < contentSync.length; i++ ) {
  317. const char = contentSync[i]
  318. lineWidth += context.measureText(char).width;
  319. // lineWidth += this._measureText(char)
  320. if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) {
  321. length += 1
  322. end = start + length;
  323. lastIndex = i + 1;
  324. break;
  325. } else if ( lineWidth >= this._contentWidth ) {
  326. lastIndex = i;
  327. break;
  328. } else {
  329. text[text.length - 1] += char
  330. length += 1;
  331. end = start + length;
  332. }
  333. }
  334. pageHeight += this.fontSize + this.lineGap
  335. if ( end >= contentSync.length - 1 + start ) break;
  336. }
  337. return {start, end, text};
  338. },
  339. enumerable:false
  340. })
  341. //计算上一页的文字排版
  342. Object.defineProperty(Wholereader.prototype,'_computedPrevText',{
  343. value:function(end){
  344. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  345. throw new TypeError("TypeError: Wholereader._computedPrevText is not a constructor")
  346. }
  347. const context = this._createComputedCanvas()
  348. let pageHeight = this.fontSize + this.lineGap, text = [], start = 0, length = 0, lastIndex1 = 0, lastIndex2 = 0
  349. while ( pageHeight <= this._contentHeight ) {
  350. if ( end - length > 0 ) {
  351. text.unshift('');
  352. let lineWidth = 0, contentSync = this._contents.slice(0, end)
  353. for ( let i = end - length - 1; i >= 0; i-- ) {
  354. const char = contentSync[i]
  355. lineWidth += context.measureText(char).width;
  356. // lineWidth += this._measureText(char)
  357. if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) {
  358. lastIndex1 = i - 1;
  359. length += 1
  360. break;
  361. } else if ( lineWidth >= this._contentWidth ) {
  362. lastIndex1 = i;
  363. break;
  364. } else {
  365. text[0] = char + text[0];
  366. length += 1
  367. start = end - length;
  368. }
  369. if ( start == 0 ) break;
  370. }
  371. pageHeight += this.fontSize + this.lineGap
  372. } else {
  373. if ( this.pageType != 'scroll' ) {
  374. text.push('');
  375. let lineWidth = 0, contentSync = this._contents.slice(end)
  376. for ( let i = lastIndex2; i < contentSync.length; i++ ) {
  377. const char = contentSync[i]
  378. lineWidth += context.measureText(char).width;
  379. // lineWidth += this._measureText(char)
  380. if ( JSON.stringify(char) == JSON.stringify('\r') || JSON.stringify(char) == JSON.stringify('\n') ) {
  381. lastIndex2 = i + 1;
  382. length += 1
  383. break;
  384. } else if ( lineWidth >= this._contentWidth ) {
  385. lastIndex2 = i;
  386. break;
  387. } else {
  388. text[text.length - 1] += char;
  389. length += 1;
  390. end = start + length;
  391. }
  392. }
  393. pageHeight += this.fontSize + this.lineGap
  394. if ( end >= this._contents.length - 1 ) break;
  395. } else break;
  396. }
  397. }
  398. return {start, end, text}
  399. },
  400. enumerable:false
  401. })
  402. //创建页面元素
  403. Object.defineProperty(Wholereader.prototype,'_createPageDom',{
  404. value:async function(page){
  405. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  406. throw new TypeError("TypeError: Wholereader._createComputedCanvas is not a constructor")
  407. }
  408. //创建页面盒子
  409. const item = document.createElement('DIV');
  410. item.style.zIndex = this._contents.length - page.start;
  411. 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' : ''));
  412. item.setAttribute('start', page.start);
  413. item.setAttribute('end', page.end);
  414. //创建页面内容
  415. const content = document.createElement('div');
  416. content.setAttribute('class', 'whole-flip-reader-page-item-content')
  417. content.style.width = this._viewWidth + 'px';
  418. content.style.height = this._viewHeight + 'px';
  419. content.style.background = this.background
  420. content.style.color = this.color
  421. content.style.padding = `${this.topGap}px ${this.slide}px ${this.topGap}px ${this.slide}px`;
  422. if ( this.headerShow ) {//开启头部显示
  423. const header = this._createHeaderDom();
  424. content.appendChild(header);
  425. }
  426. //创建文字元素
  427. const text = this._createTextDom(page);
  428. content.appendChild(text)
  429. if ( this.footerShow ) {//开启底部显示
  430. const footer = await this._createFooterDom(page.start)
  431. content.appendChild(footer)
  432. }
  433. item.appendChild(content);
  434. //创建背景
  435. const bg = document.createElement('DIV');
  436. bg.setAttribute('class', 'whole-flip-reader-page-item-bg')
  437. bg.style.height = Math.sqrt(Math.pow(this._viewHeight, 2) + Math.pow(this._viewWidth, 2)) + 'px';
  438. bg.style.background = this.background;
  439. item.appendChild(bg);
  440. //创建阴影区域
  441. const shadow = document.createElement('DIV');
  442. shadow.setAttribute('class', 'whole-flip-reader-page-item-shadow')
  443. item.appendChild(shadow);
  444. if ( page.start < this._start ) this._pageAnimation(-this._viewWidth, 0, { box: item, content, bg, shadow })//如果是上一页内容则设置已经翻页样式
  445. return item
  446. },
  447. enumerable:false
  448. })
  449. //创建头部元素
  450. Object.defineProperty(Wholereader.prototype,'_createHeaderDom',{
  451. value:function(){
  452. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  453. throw new TypeError("TypeError: Wholereader._createHeaderDom is not a constructor")
  454. }
  455. const header = document.createElement('DIV');
  456. header.setAttribute('class', 'whole-reader-header')
  457. header.innerHTML = `<span class="whole-reader-header-text" style="color: ${this.color}">${this.title}</span>`
  458. if ( this.backShow ) {
  459. const back = document.createElement('DIV')
  460. back.setAttribute('class', 'whole-reader-header-back')
  461. back.style.borderTopColor = this.color
  462. back.style.borderLeftColor = this.color
  463. back.ontouchstart = function (e) { e.stopPropagation && e.stopPropagation(); }
  464. back.ontouchmove = function (e) { e.stopPropagation && e.stopPropagation(); }
  465. back.onmousedown = function (e) { e.stopPropagation && e.stopPropagation(); }
  466. back.onmousemove = function (e) { e.stopPropagation && e.stopPropagation(); }
  467. back.ontouchend = (e) => {
  468. e.stopPropagation && e.stopPropagation();
  469. window.setTimeout(() => { this._eventCallback.back && this._eventCallback.back() }, 50)//不加延迟可能会造成返回或者跳转页面时,触发页面相同位置点击事件
  470. }
  471. back.onmouseup = (e) => {
  472. e.stopPropagation && e.stopPropagation();
  473. if ( !this._mousedown ) return
  474. window.setTimeout(() => { this._eventCallback.back && this._eventCallback.back() }, 50)//不加延迟可能会造成返回或者跳转页面时,触发页面相同位置点击事件
  475. }
  476. header.insertBefore(back, header.firstChild)
  477. }
  478. // if ( this.backShow ) header.innerHTML = `<div class="whole-reader-header-back" style="border-top-color: ${this.color};border-left-color: ${this.color}"></div>` + header.innerHTML
  479. return header
  480. },
  481. enumerable:false
  482. })
  483. //创建底部元素
  484. Object.defineProperty(Wholereader.prototype,'_createFooterDom',{
  485. value:async function(start){
  486. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  487. throw new TypeError("TypeError: Wholereader._createFooterDom is not a constructor")
  488. }
  489. const progress = ((start / this._contents.length) * 100).toFixed(2)
  490. const d = new Date()
  491. const time = (d.getHours() < 10 ? ('0' + d.getHours()) : d.getHours()) + ':' + (d.getMinutes() < 10 ? ('0' + d.getMinutes()) : d.getMinutes())
  492. const footer = document.createElement('DIV');
  493. footer.setAttribute('class', 'whole-reader-footer')
  494. footer.innerHTML = `
  495. <div class="whole-reader-footer-left">
  496. ${await this._createBatteryDom()}
  497. <span class="whole-reader-footer-text" style="color: ${this.color}">${time}</span>
  498. </div>
  499. <span class="whole-reader-footer-text">${progress}%</span>
  500. `
  501. return footer
  502. },
  503. enumerable:false
  504. })
  505. //创建电池元素
  506. Object.defineProperty(Wholereader.prototype,'_createBatteryDom',{
  507. value: async function(){
  508. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  509. throw new TypeError("TypeError: Wholereader._createBatteryDom is not a constructor")
  510. }
  511. const max = 16
  512. const res = window.navigator.getBattery ? await window.navigator.getBattery() : {level: 1}
  513. const value = res.level * max
  514. return `
  515. <div class="whole-reader-battery">
  516. <div class="whole-reader-battery-wrapper" :style="border-color: ${this.color}">
  517. <div class="whole-reader-battery-content">
  518. <div class="whole-reader-battery-content-value" style="background-color: ${this.color};width: ${value}px"></div>
  519. </div>
  520. </div>
  521. <div class="whole-reader-battery-top" style="background-color: ${this.color}"></div>
  522. </div>
  523. `
  524. },
  525. enumerable:false
  526. })
  527. //创建文字元素
  528. Object.defineProperty(Wholereader.prototype,'_createTextDom',{
  529. value:function(page){
  530. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  531. throw new TypeError("TypeError: Wholereader._createTextDom is not a constructor")
  532. }
  533. const dom = document.createElement('DIV')
  534. dom.setAttribute('class', this.pageType == 'scroll' ? 'whole-scroll-reader-content-text' : 'whole-flip-reader-content-text')
  535. dom.setAttribute('start', page.start)
  536. dom.setAttribute('end', page.end)
  537. page.text.forEach(t => {
  538. const p = document.createElement('P');
  539. p.style.height = this.fontSize + 'px';
  540. p.style.marginTop = this.lineGap + 'px';
  541. p.style.fontSize = this.fontSize + 'px';
  542. p.style.fontFamily = this.fontFamily;
  543. p.style.whiteSpace = 'pre-wrap';
  544. p.innerHTML = t || ' ';
  545. dom.appendChild(p)
  546. })
  547. return dom
  548. },
  549. enumerable:false
  550. })
  551. //滚动模式滚动事件
  552. Object.defineProperty(Wholereader.prototype,'_scroll',{
  553. value: async function(e){
  554. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  555. throw new TypeError("TypeError: Wholereader._scroll is not a constructor")
  556. }
  557. try{
  558. const scrollItems = this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text');
  559. const scrollTop = this._scrollerEl.scrollTop + this.topGap + this.bottomGap + (this.headerShow ? 30 : 0) + (this.footerShow ? 30 : 0)
  560. for ( let i = 0; i < scrollItems.length; i++ ) {
  561. const offsetTop1 = scrollItems[i].offsetTop;
  562. const offsetTop2 = i < scrollItems.length - 1 ? scrollItems[i+1].offsetTop : offsetTop1 + 2;
  563. if ( scrollTop >= offsetTop1 && scrollTop < offsetTop2 ) {
  564. const start = parseInt(scrollItems[i].getAttribute('start'));
  565. const end = parseInt(scrollItems[i].getAttribute('end'));
  566. if ( this._start != start ) {
  567. this._start = start
  568. this._end = end
  569. if ( this.footerShow ) {//如果页面位置发生改变,则更新footer
  570. const newFooter = await this._createFooterDom(start)
  571. const oldFooter = this._wrapperEl.getElementsByClassName('whole-reader-footer')[0]
  572. this._wrapperEl.removeChild(oldFooter)
  573. this._wrapperEl.appendChild(newFooter)
  574. }
  575. this._pageChange()
  576. }
  577. }
  578. }
  579. if ( Math.ceil(this._scrollerEl.scrollTop + this._scrollerEl.offsetHeight) >= this._scrollerEl.scrollHeight ) {//触底
  580. if ( this._updownloading ) return;
  581. this._updownloading = true;
  582. const end = parseInt(this._scrollerEl.lastChild.getAttribute('end'));
  583. if ( end < this._contents.length - 1 ) {
  584. const page = this._computedNextText(end)
  585. const item = this._createTextDom(page)
  586. this._scrollerEl.appendChild(item)
  587. if ( this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text').length > 3 ) this._scrollerEl.removeChild(this._scrollerEl.firstChild);
  588. this._scrollerEl.scrollTop = this._scrollerEl.scrollHeight - this._scrollerEl.lastChild.offsetHeight - this._scrollerEl.offsetHeight;
  589. } else this._eventCallback.ended && this._eventCallback.ended()//后翻页完成事件
  590. this._updownloading = false;
  591. }
  592. if ( this._scrollerEl.scrollTop <= 0 ) {//触顶
  593. if ( this._updownloading ) return
  594. this._updownloading = true;
  595. const start = parseInt(this._scrollerEl.firstChild.getAttribute('start'));
  596. if ( start > 0 ) {
  597. const page = this._computedPrevText(start)
  598. const item = this._createTextDom(page)
  599. this._scrollerEl.insertBefore(item, this._scrollerEl.firstChild)
  600. this._scrollerEl.scrollTop = item.offsetHeight
  601. if ( this._scrollerEl.getElementsByClassName('whole-scroll-reader-content-text').length > 3 ) this._scrollerEl.removeChild(this._scrollerEl.lastChild);
  602. } else this._eventCallback.started && this._eventCallback.started()//前翻页完成事件
  603. this._updownloading = false;
  604. }
  605. }catch(e){
  606. //TODO handle the exception
  607. }
  608. },
  609. enumerable:false
  610. })
  611. //翻页模式触摸开始事件
  612. Object.defineProperty(Wholereader.prototype,'_touchstart',{
  613. value:function(e){
  614. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  615. throw new TypeError("TypeError: Wholereader._touchstart is not a constructor")
  616. }
  617. if ( this._pageWating ) return;
  618. this._touchTimer = window.setTimeout(() => {
  619. this._touchTime = 200;
  620. }, 200)
  621. const touch = e.touches[0];
  622. this._touchstartX = touch.pageX;
  623. },
  624. enumerable:false
  625. })
  626. //翻页模式触摸滑动事件
  627. Object.defineProperty(Wholereader.prototype,'_touchmove',{
  628. value:function(e){
  629. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  630. throw new TypeError("TypeError: Wholereader._touchmove is not a constructor")
  631. }
  632. if ( this._touchstartX == 0 || (this.pageType != 'real' && this.pageType != 'cover') ) return;
  633. const touch = e.touches[0]
  634. if ( this._pageEl ) {
  635. const height = this._viewHeight / 2;
  636. const maxDeg = height / 5;
  637. const rotateZ = this._pageDirection == 'next' ? ((touch.pageY - height) / maxDeg) : -((touch.pageY - height) / maxDeg);
  638. this._moveX = touch.pageX - this._touchstartX;
  639. if ( this._pageDirection == 'next' ) this._moveX > 0 ? this._moveX = 0 : null
  640. else this._moveX < 0 ? this._moveX = 0 : null
  641. this._pageAnimation(this._moveX, rotateZ);
  642. } else {
  643. if ( touch.pageX < this._touchstartX ) {
  644. this._pageEl = this._getPageActived(0);
  645. this._pageDirection = 'next'
  646. } else {
  647. this._pageEl = this._getPageActived(-1);
  648. this._pageDirection = 'prev'
  649. }
  650. }
  651. },
  652. enumerable:false
  653. })
  654. //翻页模式触摸处理事件
  655. Object.defineProperty(Wholereader.prototype,'_touchaction',{
  656. value:function(e){
  657. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  658. throw new TypeError("TypeError: Wholereader._touchaction is not a constructor")
  659. }
  660. window.clearTimeout(this._touchTimer);
  661. this._touchTimer = null
  662. if ( this._touchstartX == 0 ) return;
  663. if ( !this._pageEl && this._touchTime < 200 && (!this.unableClickPage || this.pageType == 'none') ) {
  664. //获取点击位置,判断向哪里翻页
  665. if (this._touchstartX > (this._viewWidth / 4) * 3) {//向右翻页
  666. this._pageEl = this._getPageActived(0);
  667. this._pageDirection = 'next'
  668. }
  669. if (this._touchstartX < (this._viewWidth / 4)) {//向左翻页
  670. this._pageEl = this._getPageActived(-1);
  671. this._pageDirection = 'prev'
  672. }
  673. }
  674. this._touchstartX = 0
  675. if ( this._pageEl ) {
  676. this._pageWating = true;
  677. if ( this._touchTime < 200 ) {
  678. const duration = (this.pageType == 'real' || this.pageType == 'cover') ? 200 : 0
  679. const value = this._pageDirection == 'next' ? 1 : -1;
  680. this._pageDuration(duration);
  681. this._pageAnimation(-value * this._viewWidth);
  682. setTimeout(() => {
  683. this._changePageActived(value);
  684. this._resetPageMove();
  685. }, duration + 50)
  686. } else {
  687. const duration = (this.pageType == 'real' || this.pageType == 'cover') ? 100 : 0
  688. if ( Math.abs(this._moveX) >= this._viewWidth / 4 ) {
  689. const value = this._pageDirection == 'next' ? 1 : -1;
  690. this._pageDuration(duration);
  691. this._pageAnimation(-value * this._viewWidth);
  692. setTimeout(() => {
  693. this._changePageActived(value);
  694. this._resetPageMove();
  695. }, duration + 50)
  696. } else {
  697. this._pageDuration(duration);
  698. this._pageAnimation(0);
  699. setTimeout(() => {
  700. this._resetPageMove();
  701. }, duration + 50)
  702. }
  703. }
  704. } else {
  705. this._touchTime = 0
  706. }
  707. },
  708. enumerable:false
  709. })
  710. //设置翻页对象动画效果
  711. Object.defineProperty(Wholereader.prototype,'_pageAnimation',{
  712. value:function(moveX, rotateZ = 0, el){
  713. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  714. throw new TypeError("TypeError: Wholereader._pageAnimation is not a constructor")
  715. }
  716. const lateX = this._pageDirection == 'next' ? moveX : moveX - this._viewWidth;
  717. const pageEl = el || this._pageEl;
  718. pageEl.box.style.transform = `translateX(${lateX}px)`;
  719. pageEl.box.style.boxShadow = el ? '' : '10px 10px 20px rgba(0,0,0,.2)';
  720. pageEl.content.style.transform = this.pageType == 'real' ? `translateX(${-lateX}px)` : pageEl.content.style.transform;
  721. pageEl.bg.style.transform = this.pageType == 'real' ? `translate(${lateX}px, -50%) rotateZ(${rotateZ}deg)` : pageEl.bg.style.transform;
  722. pageEl.shadow.style.boxShadow = '0 0 60px ' + (this.pageType == 'real' ? Math.abs(lateX) > 30 ? 30 : Math.abs(lateX) : 0) + 'px rgba(0,0,0,0.5)';
  723. },
  724. enumerable:false
  725. })
  726. //设置翻页对象动画时间
  727. Object.defineProperty(Wholereader.prototype,'_pageDuration',{
  728. value:function(duration){
  729. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  730. throw new TypeError("TypeError: Wholereader._pageDuration is not a constructor")
  731. }
  732. this._pageEl.box.style.transition = duration > 0 ? 'transform ' + duration + 'ms' : '';
  733. this._pageEl.content.style.transition = duration > 0 ? 'transform ' + duration + 'ms' : '';
  734. this._pageEl.bg.style.transition = duration > 0 ? 'transform ' + duration + 'ms' : '';
  735. this._pageEl.shadow.style.transition = duration > 0 ? 'box-shadow ' + duration + 'ms' : '';
  736. },
  737. enumerable:false
  738. })
  739. //获取翻页对象
  740. Object.defineProperty(Wholereader.prototype,'_getPageActived',{
  741. value:function(value){
  742. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  743. throw new TypeError("TypeError: Wholereader._getPageActived is not a constructor")
  744. }
  745. const boxs = this.container.getElementsByClassName('whole-flip-reader-page-item');
  746. for ( let i = 0; i < boxs.length; i++ ) {
  747. if ( boxs[i].getAttribute('class').indexOf('whole-flip-reader-page-item-actived') > 1 ) {
  748. if ( boxs[i + value + 1] && boxs[i + value] ) {
  749. return {
  750. box: boxs[i + value],
  751. content: boxs[i + value].getElementsByClassName('whole-flip-reader-page-item-content')[0],
  752. bg: boxs[i + value].getElementsByClassName('whole-flip-reader-page-item-bg')[0],
  753. shadow: boxs[i + value].getElementsByClassName('whole-flip-reader-page-item-shadow')[0]
  754. };
  755. }
  756. }
  757. }
  758. if ( value < 0 ) this._eventCallback.started && this._eventCallback.started()//前翻页完成事件
  759. else this._eventCallback.ended && this._eventCallback.ended()//后翻页完成事件
  760. return false;
  761. },
  762. enumerable:false
  763. })
  764. //改变翻页对象
  765. Object.defineProperty(Wholereader.prototype,'_changePageActived',{
  766. value:async function(value){
  767. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  768. throw new TypeError("TypeError: Wholereader._changePageActived is not a constructor")
  769. }
  770. const boxs = this.container.getElementsByClassName('whole-flip-reader-page-item');
  771. let index = -1
  772. for ( let i = 0; i < boxs.length; i++ ) if ( boxs[i].getAttribute('class').indexOf('page-item-actived') > -1 ) index = i
  773. boxs[index].setAttribute('class', boxs[index].getAttribute('class').replace('whole-flip-reader-page-item-actived', ''));
  774. boxs[index + value].setAttribute('class', boxs[index + value].getAttribute('class') + ' whole-flip-reader-page-item-actived');
  775. this._start = parseInt(boxs[index + value].getAttribute('start'));
  776. this._end = parseInt(boxs[index + value].getAttribute('end'));
  777. this._pageChange()
  778. if ( value < 0 && !boxs[index + value - 1] ) {//向前翻页
  779. if ( this._updownloading ) return;
  780. this._updownloading = true;
  781. const start = parseInt(this._wrapperEl.firstChild.getAttribute('start'));
  782. if ( start > 0 ) {
  783. const page = this._computedPrevText(start)
  784. const item = await this._createPageDom(page)
  785. this._wrapperEl.insertBefore(item, this._wrapperEl.firstChild)
  786. if ( this._wrapperEl.getElementsByClassName('whole-flip-reader-page-item').length > 3 ) this._wrapperEl.removeChild(this._wrapperEl.lastChild);
  787. }
  788. this._updownloading = false;
  789. }
  790. if ( value > 0 && !boxs[index + value + 1] ) {//向后翻页
  791. if ( this._updownloading ) return;
  792. this._updownloading = true;
  793. const end = parseInt(this._wrapperEl.lastChild.getAttribute('end'));
  794. if ( end < this._contents.length - 1 ) {
  795. const page = this._computedNextText(end)
  796. const item = await this._createPageDom(page)
  797. this._wrapperEl.appendChild(item)
  798. if ( this._wrapperEl.getElementsByClassName('whole-flip-reader-page-item').length > 3 ) this._wrapperEl.removeChild(this._wrapperEl.firstChild);
  799. }
  800. this._updownloading = false;
  801. }
  802. if ( value < 0 && boxs[index + value].getAttribute('end') != boxs[index + value + 1].getAttribute('start') ) this.refresh()//如果是向前翻页并且前一页结束位置和当前页开始位置不对应时刷新页面
  803. },
  804. enumerable:false
  805. })
  806. //页面改变事件
  807. Object.defineProperty(Wholereader.prototype,'_pageChange',{
  808. value:function(){
  809. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  810. throw new TypeError("TypeError: Wholereader._changePageActived is not a constructor")
  811. }
  812. const text = this._contents.slice(this._start, this._end)
  813. this._eventCallback.change && this._eventCallback.change({
  814. start: this._start,
  815. end: this._end,
  816. contents: text,
  817. content: text.join('')
  818. });
  819. if ( this.pageType != 'scroll' ) this._startAutoplay()
  820. },
  821. enumerable:false
  822. })
  823. //重置翻页事件
  824. Object.defineProperty(Wholereader.prototype,'_resetPageMove',{
  825. value:function(){
  826. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  827. throw new TypeError("TypeError: Wholereader._resetPageMove is not a constructor")
  828. }
  829. this._pageDuration(0)
  830. if ( this._pageEl ) this._pageEl.box.style.boxShadow = ''
  831. this._pageWating = false;
  832. this._moveX = 0;
  833. this._pageEl = '';
  834. this._pageDirection = 'next';
  835. this._touchTime = 0;
  836. this._touchstartX = 0;
  837. },
  838. enumerable:false
  839. })
  840. //将内容转化为数组
  841. Object.defineProperty(Wholereader.prototype,'_contentToArr',{
  842. value:function(){
  843. if(!(this instanceof Wholereader)){//那么相反 不是正常调用的就是错误的调用
  844. throw new TypeError("TypeError: Wholereader._contentToArr is not a constructor")
  845. }
  846. const arr = this.split ? [] : this.content.split('')
  847. if ( arr.length == 0 ) {//如果传入了分隔符
  848. let chars = ''//临时字符串
  849. for ( let i = 0; i < this.content.length; i++ ) {
  850. const char = this.content.charAt(i)
  851. if ( /\r|\n/.test(char) ) {//如果是换行符
  852. if ( chars ) arr.push(chars)//直接将先前存储的字符push进数组
  853. arr.push(char)//再将标签push进数组
  854. chars = ''//清空临时字符串
  855. } else if ( this.split.indexOf(char) > -1 ) {//如果是分隔符
  856. chars += char//将分隔符加入字符串
  857. arr.push(chars)//将字符串push进数组
  858. chars = ''//清空临时字符串
  859. } else {//其余字符先存着
  860. chars += char
  861. }
  862. }
  863. }
  864. return arr
  865. },
  866. enumerable:false
  867. })