jyf-parser.vue 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831
  1. <!--
  2. parser 主模块组件
  3. github:https://github.com/jin-yufeng/Parser
  4. docs:https://jin-yufeng.github.io/Parser
  5. 插件市场:https://ext.dcloud.net.cn/plugin?id=805
  6. author:JinYufeng
  7. update:2020/04/14
  8. -->
  9. <template>
  10. <view>
  11. <slot v-if="!nodes.length" />
  12. <!--#ifdef APP-PLUS-NVUE-->
  13. <web-view id="top" ref="web" :src="src" :style="'margin-top:-2px;height:'+height+'px'" @onPostMessage="_message" />
  14. <!--#endif-->
  15. <!--#ifndef APP-PLUS-NVUE-->
  16. <view id="top" :style="showAm+(selectable?';user-select:text;-webkit-user-select:text':'')" :animation="scaleAm" @tap="_tap"
  17. @touchstart="_touchstart" @touchmove="_touchmove">
  18. <!--#ifdef H5-->
  19. <div :id="'rtf'+uid"></div>
  20. <!--#endif-->
  21. <!--#ifndef H5-->
  22. <trees :nodes="nodes" :lazy-load="lazyLoad" :loadVideo="loadVideo" />
  23. <image class="images" v-for="(item, index) in imgs" v-bind:key="index" :id="index" :src="item" hidden @load="_load"/>
  24. <!--#endif-->
  25. </view>
  26. <!--#endif-->
  27. </view>
  28. </template>
  29. <script>
  30. // #ifndef H5 || APP-PLUS-NVUE
  31. import trees from './libs/trees';
  32. var cache = {},
  33. // #ifdef MP-WEIXIN || MP-TOUTIAO
  34. fs = uni.getFileSystemManager ? uni.getFileSystemManager() : null,
  35. // #endif
  36. Parser = require('./libs/MpHtmlParser.js');
  37. var document; // document 补丁包 https://jin-yufeng.github.io/Parser/#/instructions?id=document
  38. // 计算 cache 的 key
  39. function hash(str) {
  40. for (var i = str.length, val = 5381; i--;)
  41. val += (val << 5) + str.charCodeAt(i);
  42. return val;
  43. }
  44. // #endif
  45. // #ifdef H5 || APP-PLUS-NVUE
  46. var rpx = uni.getSystemInfoSync().screenWidth / 750,
  47. cfg = require('./libs/config.js');
  48. // #endif
  49. // #ifdef APP-PLUS-NVUE
  50. var dom = weex.requireModule('dom');
  51. // #endif
  52. export default {
  53. name: 'parser',
  54. data() {
  55. return {
  56. // #ifdef APP-PLUS
  57. loadVideo: false,
  58. // #endif
  59. // #ifdef H5
  60. uid: this._uid,
  61. // #endif
  62. // #ifdef APP-PLUS-NVUE
  63. src: '',
  64. height: 1,
  65. // #endif
  66. // #ifndef APP-PLUS-NVUE
  67. scaleAm: '',
  68. showAm: '',
  69. imgs: [],
  70. // #endif
  71. nodes: []
  72. }
  73. },
  74. // #ifndef H5 || APP-PLUS-NVUE
  75. components: {
  76. trees
  77. },
  78. // #endif
  79. props: {
  80. 'html': null,
  81. // #ifndef MP-ALIPAY
  82. 'autopause': {
  83. type: Boolean,
  84. default: true
  85. },
  86. // #endif
  87. 'autosetTitle': {
  88. type: Boolean,
  89. default: true
  90. },
  91. // #ifndef H5 || APP-PLUS-NVUE
  92. 'compress': Number,
  93. 'useCache': Boolean,
  94. 'xml': Boolean,
  95. // #endif
  96. 'domain': String,
  97. // #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
  98. 'gestureZoom': Boolean,
  99. // #endif
  100. // #ifdef MP-WEIXIN || MP-QQ || H5 || APP-PLUS
  101. 'lazyLoad': Boolean,
  102. // #endif
  103. 'selectable': Boolean,
  104. 'tagStyle': Object,
  105. 'showWithAnimation': Boolean,
  106. 'useAnchor': Boolean
  107. },
  108. watch: {
  109. html(html) {
  110. this.setContent(html);
  111. }
  112. },
  113. mounted() {
  114. // 图片数组
  115. this.imgList = [];
  116. this.imgList.each = function(f) {
  117. for (var i = 0, len = this.length; i < len; i++)
  118. this.setItem(i, f(this[i], i, this));
  119. }
  120. this.imgList.setItem = function(i, src) {
  121. if (i == void 0 || !src) return;
  122. // #ifndef MP-ALIPAY || APP-PLUS
  123. // 去重
  124. if (src.indexOf('http') == 0 && this.includes(src)) {
  125. var newSrc = '';
  126. for (var j = 0, c; c = src[j]; j++) {
  127. if (c == '/' && src[j - 1] != '/' && src[j + 1] != '/') break;
  128. newSrc += Math.random() > 0.5 ? c.toUpperCase() : c;
  129. }
  130. newSrc += src.substr(j);
  131. return this[i] = newSrc;
  132. }
  133. // #endif
  134. this[i] = src;
  135. // 暂存 data src
  136. if (src.includes('data:image')) {
  137. var filePath, info = src.match(/data:image\/(\S+?);(\S+?),(.+)/);
  138. if (!info) return;
  139. // #ifdef MP-WEIXIN || MP-TOUTIAO
  140. filePath = `${wx.env.USER_DATA_PATH}/${Date.now()}.${info[1]}`;
  141. fs && fs.writeFile({
  142. filePath,
  143. data: info[3],
  144. encoding: info[2],
  145. success: () => this[i] = filePath
  146. })
  147. // #endif
  148. // #ifdef APP-PLUS
  149. filePath = `_doc/parser_tmp/${Date.now()}.${info[1]}`;
  150. var bitmap = new plus.nativeObj.Bitmap();
  151. bitmap.loadBase64Data(src, () => {
  152. bitmap.save(filePath, {}, () => {
  153. bitmap.clear()
  154. this[i] = filePath;
  155. })
  156. })
  157. // #endif
  158. }
  159. }
  160. if (this.html) this.setContent(this.html);
  161. },
  162. beforeDestroy() {
  163. // #ifdef H5
  164. if (this._observer) this._observer.disconnect();
  165. // #endif
  166. this.imgList.each(src => {
  167. // #ifdef APP-PLUS
  168. if (src && src.includes('_doc')) {
  169. plus.io.resolveLocalFileSystemURL(src, entry => {
  170. entry.remove();
  171. });
  172. }
  173. // #endif
  174. // #ifdef MP-WEIXIN || MP-TOUTIAO
  175. if (src && src.includes(uni.env.USER_DATA_PATH))
  176. fs && fs.unlink({
  177. filePath: src
  178. })
  179. // #endif
  180. })
  181. clearInterval(this._timer);
  182. },
  183. methods: {
  184. // #ifdef H5 || APP-PLUS-NVUE
  185. _Dom2Str(nodes) {
  186. var str = '';
  187. for (var node of nodes) {
  188. if (node.type == 'text')
  189. str += node.text;
  190. else {
  191. str += ('<' + node.name);
  192. for (var attr in node.attrs || {})
  193. str += (' ' + attr + '="' + node.attrs[attr] + '"');
  194. if (!node.children || !node.children.length) str += '>';
  195. else str += ('>' + this._Dom2Str(node.children) + '</' + node.name + '>');
  196. }
  197. }
  198. return str;
  199. },
  200. _handleHtml(html, append) {
  201. if (typeof html != 'string') html = this._Dom2Str(html.nodes || html);
  202. // 处理 rpx
  203. if (html.includes('rpx'))
  204. html = html.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * rpx + 'px');
  205. if (!append) {
  206. // 处理 tag-style 和 userAgentStyles
  207. var style = '<style>@keyframes show{0%{opacity:0}100%{opacity:1}}';
  208. for (var item in cfg.userAgentStyles)
  209. style += `${item}{${cfg.userAgentStyles[item]}}`;
  210. for (item in this.tagStyle)
  211. style += `${item}{${this.tagStyle[item]}}`;
  212. style += '</style>';
  213. html = style + html;
  214. }
  215. return html;
  216. },
  217. // #endif
  218. setContent(html, append) {
  219. // #ifdef APP-PLUS-NVUE
  220. if (!html) {
  221. this.src = '';
  222. this.height = 1;
  223. return;
  224. }
  225. if (append) return;
  226. plus.io.resolveLocalFileSystemURL('_doc', entry => {
  227. entry.getDirectory('parser_tmp', {
  228. create: true
  229. }, entry => {
  230. var fileName = Date.now() + '.html';
  231. entry.getFile(fileName, {
  232. create: true
  233. }, entry => {
  234. entry.createWriter(writer => {
  235. writer.onwriteend = () => {
  236. this.nodes = [1];
  237. this.src = '_doc/parser_tmp/' + fileName;
  238. this.$nextTick(function() {
  239. entry.remove();
  240. })
  241. }
  242. html =
  243. '<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,user-scalable=0' +
  244. (this.selectable ? '' : ',user-scalable=no') +
  245. '"><script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js"></' +
  246. 'script><base href="' + this.domain + '">' + this._handleHtml(html) +
  247. '<script>"use strict";function post(t){uni.postMessage({data:t})}' +
  248. (this.showWithAnimation ? 'document.body.style.animation="show .5s",' : '') +
  249. 'document.addEventListener("UniAppJSBridgeReady",function(){post({action:"load",text:document.body.innerText});var t=document.getElementsByTagName("title");t.length&&post({action:"getTitle",title:t[0].innerText});for(var e,o=document.getElementsByTagName("img"),n=[],i=0,r=0;e=o[i];i++)e.onerror=function(){post({action:"error",source:"img",target:this})},e.hasAttribute("ignore")||"A"==e.parentElement.nodeName||(e.i=r++,n.push(e.src),e.onclick=function(){post({action:"preview",img:{i:this.i,src:this.src}})});post({action:"getImgList",imgList:n});for(var a,s=document.getElementsByTagName("a"),c=0;a=s[c];c++)a.onclick=function(){var t,e=this.getAttribute("href");if("#"==e[0]){var r=document.getElementById(e.substr(1));r&&(t=r.offsetTop)}return post({action:"linkpress",href:e,offset:t}),!1};;for(var u,m=document.getElementsByTagName("video"),d=0;u=m[d];d++)u.style.maxWidth="100%",u.onerror=function(){post({action:"error",source:"video",target:this})}' +
  250. (this.autopause ? ',u.onplay=function(){for(var t,e=0;t=m[e];e++)t!=this&&t.pause()}' : '') +
  251. ';for(var g,l=document.getElementsByTagName("audio"),p=0;g=l[p];p++)g.onerror=function(){post({action:"error",source:"audio",target:this})};window.onload=function(){post({action:"ready",height:document.body.scrollHeight})}});</' +
  252. 'script>';
  253. writer.write(html);
  254. });
  255. })
  256. })
  257. })
  258. // #endif
  259. // #ifdef H5
  260. if (!html) {
  261. if (this.rtf && !append) this.rtf.parentNode.removeChild(this.rtf);
  262. return;
  263. }
  264. var div = document.createElement('div');
  265. if (!append) {
  266. if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
  267. this.rtf = div;
  268. } else {
  269. if (!this.rtf) this.rtf = div;
  270. else this.rtf.appendChild(div);
  271. }
  272. div.innerHTML = this._handleHtml(html, append);
  273. for (var styles = this.rtf.getElementsByTagName('style'), i = 0, style; style = styles[i++];) {
  274. style.innerHTML = style.innerHTML.replace(/body/g, '#rtf' + this._uid);
  275. style.setAttribute('scoped', 'true');
  276. }
  277. // 懒加载
  278. if (!this._observer && this.lazyLoad && IntersectionObserver) {
  279. this._observer = new IntersectionObserver(changes => {
  280. for (let item, i = 0; item = changes[i++];) {
  281. if (item.isIntersecting) {
  282. item.target.src = item.target.getAttribute('data-src');
  283. item.target.removeAttribute('data-src');
  284. this._observer.unobserve(item.target);
  285. }
  286. }
  287. }, {
  288. rootMargin: '900px 0px 900px 0px'
  289. })
  290. }
  291. var _ts = this;
  292. // 获取标题
  293. var title = this.rtf.getElementsByTagName('title');
  294. if (title.length && this.autosetTitle)
  295. uni.setNavigationBarTitle({
  296. title: title[0].innerText
  297. })
  298. // 图片处理
  299. this.imgList.length = 0;
  300. var imgs = this.rtf.getElementsByTagName('img');
  301. for (let i = 0, j = 0, img; img = imgs[i]; i++) {
  302. img.style.maxWidth = '100%';
  303. var src = img.getAttribute('src');
  304. if (this.domain && src) {
  305. if (src[0] == '/') {
  306. if (src[1] == '/')
  307. img.src = (this.domain.includes('://') ? this.domain.split('://')[0] : '') + ':' + src;
  308. else img.src = this.domain + src;
  309. } else if (!src.includes('://')) img.src = this.domain + '/' + src;
  310. }
  311. if (!img.hasAttribute('ignore') && img.parentElement.nodeName != 'A') {
  312. img.i = j++;
  313. _ts.imgList.push(img.src || img.getAttribute('data-src'));
  314. img.onclick = function() {
  315. var preview = true;
  316. this.ignore = () => preview = false;
  317. _ts.$emit('imgtap', this);
  318. if (preview) {
  319. uni.previewImage({
  320. current: this.i,
  321. urls: _ts.imgList
  322. });
  323. }
  324. }
  325. }
  326. img.onerror = function() {
  327. _ts.$emit('error', {
  328. source: 'img',
  329. target: this
  330. });
  331. }
  332. if (_ts.lazyLoad && this._observer && img.src && img.i != 0) {
  333. img.setAttribute('data-src', img.src);
  334. img.removeAttribute('src');
  335. this._observer.observe(img);
  336. }
  337. }
  338. // 链接处理
  339. var links = this.rtf.getElementsByTagName('a');
  340. for (var link of links) {
  341. link.onclick = function() {
  342. var jump = true,
  343. href = this.getAttribute('href');
  344. _ts.$emit('linkpress', {
  345. href,
  346. ignore: () => jump = false
  347. });
  348. if (jump && href) {
  349. if (href[0] == '#') {
  350. if (_ts.useAnchor) {
  351. _ts.navigateTo({
  352. id: href.substr(1)
  353. })
  354. }
  355. } else if (href.indexOf('http') == 0 || href.indexOf('//') == 0)
  356. return true;
  357. else {
  358. uni.navigateTo({
  359. url: href
  360. })
  361. }
  362. }
  363. return false;
  364. }
  365. }
  366. // 视频处理
  367. var videos = this.rtf.getElementsByTagName('video');
  368. _ts.videoContexts = videos;
  369. for (let video, i = 0; video = videos[i++];) {
  370. // video.style.maxWidth = '100%';
  371. video.style.width = '100%';
  372. video.onerror = function() {
  373. _ts.$emit('error', {
  374. source: 'video',
  375. target: this
  376. });
  377. }
  378. video.onplay = function() {
  379. if (_ts.autopause)
  380. for (let item, i = 0; item = _ts.videoContexts[i++];)
  381. if (item != this) item.pause();
  382. }
  383. }
  384. // 音频处理
  385. var audios = this.rtf.getElementsByTagName('audios');
  386. for (var audio of audios)
  387. audio.onerror = function() {
  388. _ts.$emit('error', {
  389. source: 'audio',
  390. target: this
  391. });
  392. }
  393. this.document = this.rtf;
  394. if (!append) document.getElementById('rtf' + this._uid).appendChild(this.rtf);
  395. this.$nextTick(() => {
  396. this.nodes = [1];
  397. this.$emit('load');
  398. })
  399. setTimeout(() => this.showAm = '', 500);
  400. // #endif
  401. // #ifndef H5 || APP-PLUS-NVUE
  402. var nodes;
  403. if (!html)
  404. return this.nodes = [];
  405. else if (typeof html == 'string') {
  406. let parser = new Parser(html, this);
  407. // 缓存读取
  408. if (this.useCache) {
  409. var hashVal = hash(html);
  410. if (cache[hashVal])
  411. nodes = cache[hashVal];
  412. else {
  413. nodes = parser.parse();
  414. cache[hashVal] = nodes;
  415. }
  416. } else nodes = parser.parse();
  417. this.$emit('parse', nodes);
  418. } else if (Object.prototype.toString.call(html) == '[object Array]') {
  419. // 非本插件产生的 array 需要进行一些转换
  420. if (html.length && html[0].PoweredBy != 'Parser') {
  421. let parser = new Parser(html, this);
  422. (function f(ns) {
  423. for (var i = 0, n; n = ns[i]; i++) {
  424. if (n.type == 'text') continue;
  425. n.attrs = n.attrs || {};
  426. for (var item in n.attrs)
  427. if (typeof n.attrs[item] != 'string') n.attrs[item] = n.attrs[item].toString();
  428. parser.matchAttr(n, parser);
  429. if (n.children && n.children.length) {
  430. parser.STACK.push(n);
  431. f(n.children);
  432. parser.popNode(parser.STACK.pop());
  433. } else n.children = void 0;
  434. }
  435. })(html);
  436. }
  437. nodes = html;
  438. } else if (typeof html == 'object' && html.nodes) {
  439. nodes = html.nodes;
  440. console.warn('错误的 html 类型:object 类型已废弃');
  441. } else
  442. return console.warn('错误的 html 类型:' + typeof html);
  443. // #ifdef APP-PLUS
  444. this.loadVideo = false;
  445. // #endif
  446. if (document) this.document = new document(this.nodes, 'nodes', this);
  447. if (append) this.nodes = this.nodes.concat(nodes);
  448. else this.nodes = nodes;
  449. if (nodes.length && nodes[0].title && this.autosetTitle)
  450. uni.setNavigationBarTitle({
  451. title: nodes[0].title
  452. })
  453. this.$nextTick(() => {
  454. this.imgList.length = 0;
  455. this.videoContexts = [];
  456. // #ifdef MP-TOUTIAO
  457. setTimeout(() => {
  458. // #endif
  459. var f = (cs) => {
  460. for (let i = 0, c; c = cs[i++];) {
  461. if (c.$options.name == 'trees') {
  462. for (var j = c.nodes.length, item; item = c.nodes[--j];) {
  463. if (item.c) continue;
  464. if (item.name == 'img') {
  465. this.imgList.setItem(item.attrs.i, item.attrs.src);
  466. // #ifndef MP-ALIPAY
  467. if (!c.observer && !c.imgLoad && item.attrs.i != '0') {
  468. if (this.lazyLoad && uni.createIntersectionObserver) {
  469. c.observer = uni.createIntersectionObserver(c);
  470. c.observer.relativeToViewport({
  471. top: 900,
  472. bottom: 900
  473. }).observe('._img', () => {
  474. c.imgLoad = true;
  475. c.observer.disconnect();
  476. })
  477. } else
  478. c.imgLoad = true;
  479. }
  480. // #endif
  481. }
  482. // #ifndef MP-ALIPAY
  483. else if (item.name == 'video') {
  484. var ctx = uni.createVideoContext(item.attrs.id, c);
  485. ctx.id = item.attrs.id;
  486. this.videoContexts.push(ctx);
  487. }
  488. // #endif
  489. // #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
  490. if (item.attrs && item.attrs.id) {
  491. this.anchors = this.anchors || [];
  492. this.anchors.push({
  493. id: item.attrs.id,
  494. node: c
  495. })
  496. }
  497. // #endif
  498. }
  499. }
  500. if (c.$children.length)
  501. f(c.$children)
  502. }
  503. }
  504. f(this.$children);
  505. // #ifdef MP-TOUTIAO
  506. }, 200)
  507. this.$emit('load');
  508. // #endif
  509. // #ifdef APP-PLUS
  510. setTimeout(() => {
  511. this.loadVideo = true;
  512. }, 3000);
  513. // #endif
  514. })
  515. // #endif
  516. // #ifndef APP-PLUS-NVUE
  517. var height;
  518. clearInterval(this._timer);
  519. this._timer = setInterval(() => {
  520. // #ifdef H5
  521. var res = [this.rtf.getBoundingClientRect()];
  522. // #endif
  523. // #ifndef H5
  524. // #ifdef APP-PLUS
  525. uni.createSelectorQuery().in(this)
  526. // #endif
  527. // #ifndef APP-PLUS
  528. this.createSelectorQuery()
  529. // #endif
  530. .select('#top').boundingClientRect().exec(res => {
  531. // #endif
  532. this.width = res[0].width;
  533. if (res[0].height == height) {
  534. this.$emit('ready', res[0])
  535. clearInterval(this._timer);
  536. }
  537. height = res[0].height;
  538. // #ifndef H5
  539. });
  540. // #endif
  541. }, 350)
  542. if (this.showWithAnimation && !append) this.showAm = 'animation:show .5s';
  543. // #endif
  544. },
  545. getText(ns = this.nodes) {
  546. // #ifdef APP-PLUS-NVUE
  547. return this._text;
  548. // #endif
  549. // #ifdef H5
  550. return this.rtf.innerText;
  551. // #endif
  552. // #ifndef H5 || APP-PLUS-NVUE
  553. var txt = '';
  554. for (var i = 0, n; n = ns[i++];) {
  555. if (n.type == 'text') txt += n.text.replace(/&nbsp;/g, '\u00A0').replace(/&lt;/g, '<').replace(/&gt;/g, '>')
  556. .replace(/&amp;/g, '&');
  557. else if (n.type == 'br') txt += '\n';
  558. else {
  559. // 块级标签前后加换行
  560. var block = n.name == 'p' || n.name == 'div' || n.name == 'tr' || n.name == 'li' || (n.name[0] == 'h' && n.name[1] >
  561. '0' && n.name[1] < '7');
  562. if (block && txt && txt[txt.length - 1] != '\n') txt += '\n';
  563. if (n.children) txt += this.getText(n.children);
  564. if (block && txt[txt.length - 1] != '\n') txt += '\n';
  565. else if (n.name == 'td' || n.name == 'th') txt += '\t';
  566. }
  567. }
  568. return txt;
  569. // #endif
  570. },
  571. navigateTo(obj) {
  572. if (!this.useAnchor)
  573. return obj.fail && obj.fail({
  574. errMsg: 'Anchor is disabled'
  575. })
  576. // #ifdef APP-PLUS-NVUE
  577. if (!obj.id)
  578. dom.scrollToElement(this.$refs.web);
  579. else
  580. this.$refs.web.evalJs('var pos=document.getElementById("' + obj.id +
  581. '");if(pos)post({action:"linkpress",href:"#",offset:pos.offsetTop})');
  582. return obj.success && obj.success({
  583. errMsg: 'pageScrollTo:ok'
  584. });
  585. // #endif
  586. // #ifdef H5
  587. if (!obj.id) {
  588. window.scrollTo(0, this.rtf.offsetTop);
  589. return obj.success && obj.success({
  590. errMsg: 'pageScrollTo:ok'
  591. });
  592. }
  593. var target = document.getElementById(obj.id);
  594. if (!target) return obj.fail && obj.fail({
  595. errMsg: 'Label not found'
  596. });
  597. obj.scrollTop = this.rtf.offsetTop + target.offsetTop;
  598. uni.pageScrollTo(obj);
  599. // #endif
  600. // #ifndef H5
  601. var Scroll = (selector, component) => {
  602. uni.createSelectorQuery().in(component ? component : this).select(selector).boundingClientRect().selectViewport()
  603. .scrollOffset()
  604. .exec(res => {
  605. if (!res || !res[0])
  606. return obj.fail && obj.fail({
  607. errMsg: 'Label not found'
  608. });
  609. obj.scrollTop = res[1].scrollTop + res[0].top;
  610. uni.pageScrollTo(obj);
  611. })
  612. }
  613. if (!obj.id) Scroll('#top');
  614. else {
  615. // #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
  616. Scroll('#top >>> #' + obj.id + ', #top >>> .' + obj.id);
  617. // #endif
  618. // #ifdef MP-BAIDU || MP-ALIPAY || APP-PLUS
  619. for (var anchor of this.anchors)
  620. if (anchor.id == obj.id)
  621. Scroll('#' + obj.id + ', .' + obj.id, anchor.node);
  622. // #endif
  623. }
  624. // #endif
  625. },
  626. getVideoContext(id) {
  627. // #ifndef APP-PLUS-NVUE
  628. if (!id) return this.videoContexts;
  629. else
  630. for (var i = this.videoContexts.length; i--;)
  631. if (this.videoContexts[i].id == id) return this.videoContexts[i];
  632. // #endif
  633. },
  634. // 预加载
  635. preLoad(html, num) {
  636. // #ifdef H5 || APP-PLUS-NVUE
  637. if (html.constructor == Array)
  638. html = this._Dom2Str(html);
  639. var script = "var contain=document.createElement('div');contain.innerHTML='" + html.replace(/'/g, "\\'") +
  640. "';for(var imgs=contain.querySelectorAll('img'),i=imgs.length-1;i>=" + num +
  641. ";i--)imgs[i].removeAttribute('src');";
  642. // #endif
  643. // #ifdef APP-PLUS-NVUE
  644. this.$refs.web.evalJs(script);
  645. // #endif
  646. // #ifdef H5
  647. eval(script);
  648. // #endif
  649. // #ifndef H5 || APP-PLUS-NVUE
  650. if (typeof html == 'string') {
  651. var id = hash(html);
  652. html = new Parser(html, this).parse();
  653. cache[id] = html;
  654. }
  655. var wait = [];
  656. (function f(ns) {
  657. for (var i = 0, n; n = ns[i++];) {
  658. if (n.name == 'img' && n.attrs.src && !wait.includes(n.attrs.src))
  659. wait.push(n.attrs.src);
  660. f(n.children || []);
  661. }
  662. })(html);
  663. if (num) wait = wait.slice(0, num);
  664. this._wait = (this._wait || []).concat(wait);
  665. if (!this.imgs) this.imgs = this._wait.splice(0, 15);
  666. else if (this.imgs.length < 15)
  667. this.imgs = this.imgs.concat(this._wait.splice(0, 15 - this.imgs.length));
  668. // #endif
  669. },
  670. // #ifdef APP-PLUS-NVUE
  671. _message(e) {
  672. // 接收 web-view 消息
  673. var data = e.detail.data[0];
  674. if (data.action == 'load') {
  675. this.$emit('load');
  676. this._text = data.text;
  677. } else if (data.action == 'getTitle') {
  678. if (this.autosetTitle)
  679. uni.setNavigationBarTitle({
  680. title: data.title
  681. })
  682. } else if (data.action == 'getImgList') {
  683. this.imgList.length = 0;
  684. for (var i = data.imgList.length; i--;)
  685. this.imgList.setItem(i, data.imgList[i]);
  686. } else if (data.action == 'preview') {
  687. var preview = true;
  688. data.img.ignore = () => preview = false;
  689. this.$emit('imgtap', data.img);
  690. if (preview)
  691. uni.previewImage({
  692. current: data.img.i,
  693. urls: this.imgList
  694. })
  695. } else if (data.action == 'linkpress') {
  696. var jump = true,
  697. href = data.href;
  698. this.$emit('linkpress', {
  699. href,
  700. ignore: () => jump = false
  701. })
  702. if (jump && href) {
  703. if (href[0] == '#') {
  704. if (this.useAnchor)
  705. dom.scrollToElement(this.$refs.web, {
  706. offset: data.offset
  707. })
  708. } else if (href.includes('://'))
  709. plus.runtime.openWeb(href);
  710. else
  711. uni.navigateTo({
  712. url: href
  713. })
  714. }
  715. } else if (data.action == 'error')
  716. this.$emit('error', {
  717. source: data.source,
  718. target: data.target
  719. })
  720. else if (data.action == 'ready') {
  721. this.height = data.height;
  722. this.$nextTick(() => {
  723. uni.createSelectorQuery().in(this).select('#top').boundingClientRect().exec(res => {
  724. this.rect = res[0];
  725. this.$emit('ready', res[0]);
  726. })
  727. })
  728. }
  729. },
  730. // #endif
  731. // #ifndef APP-PLUS-NVUE
  732. // #ifndef H5
  733. _load(e) {
  734. if (this._wait.length)
  735. this.$set(this.imgs, e.target.id, this._wait.shift());
  736. },
  737. // #endif
  738. _tap(e) {
  739. // #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
  740. if (this.gestureZoom && e.timeStamp - this._lastT < 300) {
  741. var initY = e.touches[0].pageY - e.currentTarget.offsetTop;
  742. if (this._zoom) {
  743. this._scaleAm.translateX(0).scale(1).step();
  744. uni.pageScrollTo({
  745. scrollTop: (initY + this._initY) / 2 - e.touches[0].clientY,
  746. duration: 400
  747. })
  748. } else {
  749. var initX = e.touches[0].pageX - e.currentTarget.offsetLeft;
  750. this._initY = initY;
  751. this._scaleAm = uni.createAnimation({
  752. transformOrigin: `${initX}px ${this._initY}px 0`,
  753. timingFunction: 'ease-in-out'
  754. });
  755. // #ifdef MP-TOUTIAO
  756. this._scaleAm.opacity(1);
  757. // #endif
  758. this._scaleAm.scale(2).step();
  759. this._tMax = initX / 2;
  760. this._tMin = (initX - this.width) / 2;
  761. this._tX = 0;
  762. }
  763. this._zoom = !this._zoom;
  764. this.scaleAm = this._scaleAm.export();
  765. }
  766. this._lastT = e.timeStamp;
  767. // #endif
  768. },
  769. _touchstart(e) {
  770. // #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
  771. if (e.touches.length == 1)
  772. this._initX = this._lastX = e.touches[0].pageX;
  773. // #endif
  774. },
  775. _touchmove(e) {
  776. // #ifndef MP-BAIDU || MP-ALIPAY || APP-PLUS
  777. var diff = e.touches[0].pageX - this._lastX;
  778. if (this._zoom && e.touches.length == 1 && Math.abs(diff) > 20) {
  779. this._lastX = e.touches[0].pageX;
  780. if ((this._tX <= this._tMin && diff < 0) || (this._tX >= this._tMax && diff > 0))
  781. return;
  782. this._tX += (diff * Math.abs(this._lastX - this._initX) * 0.05);
  783. if (this._tX < this._tMin) this._tX = this._tMin;
  784. if (this._tX > this._tMax) this._tX = this._tMax;
  785. this._scaleAm.translateX(this._tX).step();
  786. this.scaleAm = this._scaleAm.export();
  787. }
  788. // #endif
  789. }
  790. // #endif
  791. }
  792. }
  793. </script>
  794. <style>
  795. @keyframes show {
  796. 0% {
  797. opacity: 0
  798. }
  799. 100% {
  800. opacity: 1;
  801. }
  802. }
  803. video{
  804. width: 750rpx;
  805. }
  806. /* #ifdef MP-*/
  807. .images{
  808. display: block;
  809. vertical-align: top;
  810. vertical-align: text-top;
  811. vertical-align: bottom;
  812. vertical-align: text-bottom;
  813. float: left;
  814. }
  815. #top{
  816. overflow: hidden;
  817. }
  818. /* #endif */
  819. /* #ifdef MP-WEIXIN */
  820. :host {
  821. display: block;
  822. overflow: scroll;
  823. -webkit-overflow-scrolling: touch;
  824. }
  825. /* #endif */
  826. </style>