/* parser 主组件 github:https://github.com/jin-yufeng/Parser docs:https://jin-yufeng.github.io/Parser author:JinYufeng update:2020/04/25 */ var cache = {}, Parser = require('./libs/MpHtmlParser.js'), fs = wx.getFileSystemManager && wx.getFileSystemManager(); try { var dom = require('./libs/document.js'); } catch (e) {} // 计算 cache 的 key function hash(str) { for (var i = str.length, val = 5381; i--;) val += (val << 5) + str.charCodeAt(i); return val; } Component({ options: { pureDataPattern: /^[acdgtux]|W/ }, properties: { 'html': { type: null, observer(html) { if (this._refresh) this._refresh = false; else this.setContent(html, false, true); } }, 'autosetTitle': { type: Boolean, value: true }, 'autopause': { type: Boolean, value: true }, 'compress': Number, 'domain': String, 'gestureZoom': Boolean, 'lazyLoad': Boolean, 'selectable': Boolean, 'tagStyle': Object, 'showWithAnimation': Boolean, 'useAnchor': Boolean, 'useCache': Boolean, 'xml': Boolean }, relations: { '../parser-group/parser-group': { type: 'ancestor' } }, created() { // 图片数组 this.imgList = []; this.imgList.setItem = function (i, src) { if (!i || !src) return; // 去重 if (src.indexOf('http') == 0 && this.includes(src)) { var newSrc = ''; for (var j = 0, c; c = src[j]; j++) { if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break; newSrc += Math.random() > 0.5 ? c.toUpperCase() : c; } newSrc += src.substr(j); return this[i] = newSrc; } this[i] = src; // 暂存 data src if (src.includes('data:image')) { var info = src.match(/data:image\/(\S+?);(\S+?),(.+)/); if (!info) return; var filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`; fs && fs.writeFile({ filePath, data: info[3], encoding: info[2], success: () => this[i] = filePath }) } } this.imgList.each = function (f) { for (var i = 0, len = this.length; i < len; i++) this.setItem(i, f(this[i], i, this)); } if (dom) this.document = new dom(this); }, detached() { // 删除暂存 this.imgList.each(src => { if (src && src.includes(wx.env.USER_DATA_PATH) && fs) fs.unlink({ filePath: src }) }) clearInterval(this._timer); }, methods: { // 锚点跳转 navigateTo(obj) { if (!this.data.useAnchor) return obj.fail && obj.fail({ errMsg: 'Anchor is disabled' }) this.createSelectorQuery() .select('.top' + (obj.id ? '>>>#' + obj.id : '')).boundingClientRect() .selectViewport().scrollOffset().exec(res => { if (!res[0]) return this.group ? this.group.navigateTo(this.i, obj) : obj.fail && obj.fail({ errMsg: 'Label not found' }); obj.scrollTop = res[1].scrollTop + res[0].top + (obj.offset || 0); wx.pageScrollTo(obj); }) }, // 获取文本 getText(ns = this.data.html) { var txt = ''; for (var i = 0, n; n = ns[i++];) { if (n.type == 'text') txt += n.text.replace(/ /g, '\u00A0').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); else if (n.type == 'br') txt += '\n'; else { // 块级标签前后加换行 var br = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] > '0' && n.name[1] < '7'); if (br && txt && txt[txt.length - 1] != '\n') txt += '\n'; if (n.children) txt += this.getText(n.children); if (br && txt[txt.length - 1] != '\n') txt += '\n'; else if (n.name == 'td' || n.name == 'th') txt += '\t'; } } return txt; }, // 获取视频 context getVideoContext(id) { if (!id) return this.videoContexts; for (var i = this.videoContexts.length; i--;) if (this.videoContexts[i].id == id) return this.videoContexts[i]; }, // 渲染富文本 setContent(html, append, _watch) { var data = {}; if (!html) { if (_watch || append) return; data.html = ''; } else if (typeof html == 'string') { let parser = new Parser(html, this.data); // 缓存读取 if (this.data.useCache) { var hashVal = hash(html); if (cache[hashVal]) data.html = cache[hashVal]; else { data.html = parser.parse(); cache[hashVal] = data.html; } } else data.html = parser.parse(); this._refresh = true; this.triggerEvent('parse', data.html); } else if (html.constructor == Array) { // 转换不符合格式的 array if (html.length && html[0].PoweredBy != 'Parser') { let parser = new Parser('', this.data); (function f(ns) { for (var i = 0, n; n = ns[i]; i++) { if (n.type == 'text') continue; n.attrs = n.attrs || {}; for (var key in n.attrs) if (typeof n.attrs[key] != 'string') n.attrs[key] = n.attrs[key].toString(); parser.matchAttr(n); if (n.children) { parser.STACK.push(n); f(n.children); parser.popNode(parser.STACK.pop()); } } })(html); data.html = html; } if (!_watch) data.html = html; } else if (typeof html == 'object' && html.nodes) { data.html = html.nodes; console.warn('错误的 html 类型:object 类型已废弃'); } else return console.warn('错误的 html 类型:' + typeof html); if (append) { this._refresh = true; data.html = (this.data.html || []).concat(data.html); } else if (this.data.showWithAnimation) data.showAm = 'animation: show .5s'; if (data.html || data.showAm) this.setData(data); // 设置标题 if (this.data.html.length && this.data.html[0].title && this.data.autosetTitle) wx.setNavigationBarTitle({ title: this.data.html[0].title }) this.imgList.length = 0; this.videoContexts = []; var ns = this.selectAllComponents('.top,.top>>>._node'); for (let i = 0, n; n = ns[i++];) { n.top = this; for (var j = 0, item; item = n.data.nodes[j++];) { if (item.c) continue; // 获取图片列表 if (item.name == 'img') this.imgList.setItem(item.attrs.i, item.attrs.src); // 音视频控制 else if (item.name == 'video' || item.name == 'audio') { var ctx; if (item.name == 'video') ctx = wx.createVideoContext(item.attrs.id, n); else ctx = n.selectComponent('#' + item.attrs.id); if (ctx) { ctx.id = item.attrs.id; this.videoContexts.push(ctx); } } } } (wx.nextTick || setTimeout)(() => this.triggerEvent('load'), 50); var height; clearInterval(this._timer); this._timer = setInterval(() => { this.createSelectorQuery().select('.top').boundingClientRect(res => { this.rect = res; if (res.height == height) { this.triggerEvent('ready', res) clearInterval(this._timer); } height = res.height; }).exec(); }, 350) }, // 预加载 preLoad(html, num) { if (typeof html == 'string') { var id = hash(html); html = new Parser(html, this.data).parse(); cache[id] = html; } var imgs, wait = []; (function f(ns) { for (var i = 0, n; n = ns[i++];) { if (n.name == 'img' && n.attrs.src && !wait.includes(n.attrs.src)) wait.push(n.attrs.src); f(n.children || []); } })(html); if (num) wait = wait.slice(0, num); this._wait = (this._wait || []).concat(wait); if (!this.data.imgs) imgs = this._wait.splice(0, 15); else if (this.data.imgs.length < 15) imgs = this.data.imgs.concat(this._wait.splice(0, 15 - this.data.imgs.length)); imgs && this.setData({ imgs }); }, _load(e) { if (this._wait.length) this.setData({ [`imgs[${e.target.id}]`]: this._wait.shift() }) }, // 事件处理 _tap(e) { if (this.data.gestureZoom && e.timeStamp - this._lastT < 300) { var initY = e.detail.y - e.currentTarget.offsetTop; if (this._zoom) { this._scaleAm.translateX(0).scale(1).step(); wx.pageScrollTo({ scrollTop: (initY + this._initY) / 2 - e.touches[0].clientY, duration: 400 }) } else { var initX = e.detail.x - e.currentTarget.offsetLeft; this._initY = initY; this._scaleAm = wx.createAnimation({ transformOrigin: `${initX}px ${this._initY}px 0`, timingFunction: 'ease-in-out' }); this._scaleAm.scale(2).step(); this._tMax = initX / 2; this._tMin = (initX - this.rect.width) / 2; this._tX = 0; } this._zoom = !this._zoom; this.setData({ scaleAm: this._scaleAm.export() }) } this._lastT = e.timeStamp; }, _touchstart(e) { if (e.touches.length == 1) this._initX = this._lastX = e.touches[0].pageX; }, _touchmove(e) { var diff = e.touches[0].pageX - this._lastX; if (this._zoom && e.touches.length == 1 && Math.abs(diff) > 20) { this._lastX = e.touches[0].pageX; if ((this._tX <= this._tMin && diff < 0) || (this._tX >= this._tMax && diff > 0)) return; this._tX += diff * Math.abs(this._lastX - this._initX) * 0.05; if (this._tX < this._tMin) this._tX = this._tMin; if (this._tX > this._tMax) this._tX = this._tMax; this._scaleAm.translateX(this._tX).step(); this.setData({ scaleAm: this._scaleAm.export() }) } } } })