Pixiv鼠標預覽

滑鼠移入縮圖時彈出預覽視窗,滑動滾輪切換上下張圖片

目前为 2022-05-01 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Pixiv鼠標預覽
  3. // @namespace Pixiv鼠標預覽
  4. // @version 2.0.3
  5. // @description 滑鼠移入縮圖時彈出預覽視窗,滑動滾輪切換上下張圖片
  6. // @author Eltair
  7. // @match https://www.pixiv.net/*
  8. // @connect i.pximg.net
  9. // @grant GM_xmlhttpRequest
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // [可更改的數值]
  17. const BOXSIZE = window.innerHeight * 0.75; // 彈出框寬度,單位px
  18. const SPEED = 0.6; // 鼠標移動到圖上多久後彈出預覽框,單位秒
  19. const ADV = 2; // 多圖時,提前載入的張數
  20.  
  21. let t = null; // 計時器
  22. let zp = null; // ZipImagePlayer
  23. let css = document.createElement("style"); // css
  24. let line = document.createElement("div"); // 底線
  25.  
  26. // [設定CSS]
  27. // under css
  28. css.innerText = `@keyframes showUnder { from { width: 0%; } to { width: 100%; } } .under { background-color: #008FEA; height: 2px; position: absolute; bottom: 0; left: 0; width: 0%; } .showUnder { animation: showUnder ${SPEED}s linear; width: 100%; }` ;
  29. // box css
  30. css.innerText += ".imgbox{z-index:10001;position:absolute;text-align:center;border:1px solid #cbd3da;background-color:#f2f4f6}.imgbox p{width:100%}.imgbox p.tags{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.imgbox img{vertical-align:middle}.imgbox .dl{right:0;bottom:0;position:absolute;font-weight:700;color:#1f1f1f;cursor:pointer}.imgpv{overflow:hidden}.dl:visited{color:#adadad}.dl:hover{color:#5c5c5c}.am{display:flex;align-items:center;justify-content:center}";
  31. document.head.appendChild(css);
  32. line.className = "under showUnder";
  33.  
  34. // [綁定監聽器]
  35. if(location.pathname == "/ranking.php"){
  36. document.querySelector(".ranking-items").addEventListener("mouseover", in_img.bind(null, ["_work"]));
  37. document.querySelector(".ranking-items").addEventListener("mouseout", out_img.bind(null, ["_work"]));
  38. } else {
  39. document.querySelector("#root").addEventListener("mouseover", in_img.bind(null, ["sc-rp5asc-10", "sc-7bef31-2"]));
  40. document.querySelector("#root").addEventListener("mouseout", out_img.bind(null, ["sc-rp5asc-10", "sc-7bef31-2"]));
  41. }
  42.  
  43. // ========== 主要方法 ========== //
  44. // [滑鼠移入圖片]
  45. function in_img(target_class, e) {
  46. if (e.target.classList.contains(target_class[0]) || e.target.classList.contains(target_class[1])) {
  47. e.target.parentNode.appendChild(line);
  48. window.clearTimeout(t);
  49. t = window.setTimeout(function() {
  50. window.clearTimeout(t);
  51. new previewbox(e.target);
  52. }, SPEED * 1000)
  53. }
  54. }
  55. // [滑鼠移出圖片]
  56. function out_img(target_class, e) {
  57. if (e.target.classList.contains(target_class[0]) || e.target.classList.contains(target_class[1])) {
  58. e.target.parentNode.removeChild(line);
  59. window.clearTimeout(t);
  60. }
  61. }
  62. // [預覽框]
  63. function previewbox(target) {
  64. this.size = BOXSIZE; // 彈出框寬度,單位px
  65. this.adv = ADV; // 多圖時,提前載入的張數
  66. this.target = target;
  67. this.original_size = this.size; // 彈出框原始大小
  68. this.pid = null;
  69. this.box = null;
  70. this.box_html = "<p class='tags'>取得標籤中...</p><div class='imgpv' data-cursor='0' style='width: {size}px; height: {size}px;'></div><div style='position: relative;' class='under'></div><div><p class='count'>1/1</p><span class='dl' data-src=''>下載</span></div>";
  71. this.img_html = "<a href='{href}'><img src='{imgsrc}' style='max-width: {size}px; max-height: {size}px;'></a>";
  72. this._createBox();
  73. return this;
  74. }
  75. // [加入彈出框]
  76. previewbox.prototype._createBox = function() {
  77. let pos = this._calcPosition();
  78. this.pid = this._getId();
  79. this.box = document.createElement("div");
  80. this.box.className = "imgbox";
  81. this.box.innerHTML = this.box_html.replace(/{size}/g, this.size);
  82. this.box.style.top = `${pos.top}px`;
  83. this.box.style.left = `${pos.left}px`;
  84. this.box.style.width = `${this.size + 2}px`;
  85. this.box.addEventListener("mouseleave", this._removeBox);
  86. document.querySelector("body").appendChild(this.box);
  87. this._getData(function(json) {
  88. let body = json.body;
  89. // 動圖或是一般插畫
  90. if(body.illustType == 2) {
  91. this._addAm(body.urls.small);
  92. } else if(body.illustType == 0 || body.illustType == 1) {
  93. this._addImgs(this._getImgurls(body.urls.original, body.pageCount));
  94. }
  95. // 設定標籤
  96. this._addTags(body.tags.tags);
  97. }.bind(this));
  98. }
  99. // [刪除彈出框]
  100. previewbox.prototype._removeBox = function() {
  101. this != null && this.remove();
  102. zp && zp.loadAbort();
  103. zp && window.clearTimeout(zp._loadTimer);
  104. zp && window.clearTimeout(zp._timer);
  105. zp = null;
  106. }
  107. // [計算彈出框位置]
  108. previewbox.prototype._calcPosition = function() {
  109. let windowWidth = window.innerWidth,
  110. windowHeight = window.innerHeight,
  111. bounding = this.target.getBoundingClientRect(),
  112. scrollTop = document.documentElement.scrollTop,
  113. scrollLeft = document.documentElement.scrollLeft,
  114. top = 0,
  115. left = 0;
  116. this.size = this.original_size;
  117. if (windowWidth < this.size || windowHeight < this.size) {
  118. this.size = Math.min(windowWidth, windowHeight) / 100 * 80;
  119. }
  120. left = scrollLeft + bounding.left - (this.size - bounding.width) / 2;
  121. top = scrollTop + bounding.top - (this.size - bounding.height) / 2;
  122. // 如果上邊超出視窗
  123. if (top < scrollTop) {
  124. top = scrollTop;
  125. }
  126. // 如果下邊超出視窗
  127. if (top + (this.size + 38) > scrollTop + windowHeight) {
  128. top = windowHeight - (this.size + 38) + scrollTop - (document.documentElement.scrollWidth > document.documentElement.clientWidth ? 15 : 0);
  129. }
  130. // 如果左邊超出視窗
  131. if (left < scrollLeft) {
  132. left = scrollLeft;
  133. }
  134. // 如果右邊超出視窗
  135. if (left + this.size > scrollLeft + windowWidth) {
  136. left = windowWidth - this.size + scrollLeft - 15;
  137. }
  138. return { top: top, left: left };
  139. }
  140. // [插入圖片方法]
  141. previewbox.prototype._addImg = function(url) {
  142. let div = document.createElement("div");
  143. div.style.width = `${this.size}px`;
  144. div.style.height = `${this.size}px`;
  145. div.style.lineHeight = `${this.size}px`;
  146. div.innerHTML = this.img_html.replace(/{href}/g, url[0]).replace(/{imgsrc}/g, url[1]).replace(/{size}/g, this.size);
  147. this.box.querySelector(".imgpv").appendChild(div);
  148. }
  149. // [插入多圖方法]
  150. previewbox.prototype._addImgs = function(urls) {
  151. let cursor = 0; // 當前圖片位置
  152. let imgpv = this.box.querySelector(".imgpv"); // 圖片視窗
  153. let count = this.box.querySelector(".count"); // 頁數
  154. let dl = this.box.querySelector(".dl"); // 下載
  155.  
  156. // 圖片數量標籤
  157. count.innerText = `1/${urls[1].length}`;
  158. // 圖片下載按鈕
  159. dl.dataset.src = urls[2][0];
  160. dl.addEventListener("click", function() {
  161. GM_xmlhttpRequest({
  162. method: "GET",
  163. url: this.dataset.src,
  164. headers: { referer: "https://www.pixiv.net/" },
  165. responseType: "blob",
  166. onload: function (e) {
  167. let blob = window.URL.createObjectURL(e.response);
  168. let a = document.createElement("a");
  169. a.href = blob;
  170. a.download = e.finalUrl.match(/[0-9]+\_p[0-9]+\..+/g)[0];
  171. a.click();
  172. }
  173. });
  174. });
  175.  
  176. if(urls[1].length == 1) {
  177. this._addImg([urls[0], urls[1][0]]);
  178. return;
  179. }
  180.  
  181. // 提前載入圖片
  182. for(let i = 0; i <= this.adv && i < urls[1].length; i++) {
  183. this._addImg([urls[0], urls[1][i]]);
  184. }
  185. // 滾輪移動監聽器
  186. imgpv.addEventListener("mousewheel", function(e) {
  187. e.preventDefault();
  188. // 滾輪往下或往上
  189. if (e.deltaY > 0 && cursor < urls[1].length - 1) {
  190. cursor++;
  191. if (cursor == imgpv.childNodes.length - this.adv && cursor < urls[1].length - this.adv) {
  192. this._addImg([urls[0], urls[1][cursor + this.adv]]);
  193. }
  194. imgpv.childNodes[cursor - 1].style.display = "none";
  195. imgpv.childNodes[cursor].style.display = "block";
  196. } else if (e.deltaY < 0 && cursor > 0) {
  197. cursor--;
  198. imgpv.childNodes[cursor + 1].style.display = "none";
  199. imgpv.childNodes[cursor].style.display = "block";
  200. }
  201. count.innerText = `${cursor + 1}/${urls[1].length}`;
  202. dl.dataset.src = urls[2][cursor];
  203. }.bind(this));
  204. }
  205. // [插入動圖方法]
  206. previewbox.prototype._addAm = function(imgurl) {
  207. let img = new Image();
  208. let canvas = document.createElement("canvas");
  209. let dl = this.box.querySelector(".dl"); // 下載
  210.  
  211. this.box.querySelector(".imgpv").appendChild(canvas);
  212. img.src = imgurl.replace(/540x540_70/g, "600x600");
  213. img.onload = function() {
  214. canvas.width = this.width;
  215. canvas.height = this.height;
  216. }
  217. canvas.style.maxWidth = `${this.size}px`;
  218. canvas.style.maxHeight = `${this.size}px`;
  219. canvas.parentNode.classList.add("am");
  220. fetch(`https://www.pixiv.net/ajax/illust/${this.pid}/ugoira_meta`).then(function(response) {
  221. if (!response.ok) throw new Error(response.statusText);
  222. return response.json();
  223. }).then(function(json) {
  224. let options = {
  225. canvas: canvas,
  226. source: json.body.src,
  227. metadata: json.body,
  228. chunkSize: 300000,
  229. loop: true,
  230. };
  231. let box = this.box.querySelector(".under");
  232. let count = this.box.querySelector(".count");
  233. zp = new ZipImagePlayer(options);
  234. document.addEventListener("frame", function(e) {
  235. box.style.width = `${e.detail.frame / e.detail.count * 100}%`;
  236. count.innerText = `${e.detail.frame}/${e.detail.count}`;
  237. });
  238. // 圖片下載按鈕
  239. dl.dataset.src = json.body.originalSrc; // json.body.src(小圖) or json.body.originalSrc(原圖)
  240. dl.addEventListener("click", function() {
  241. GM_xmlhttpRequest({
  242. method: "GET",
  243. url: this.dataset.src,
  244. headers: { referer: "https://www.pixiv.net/" },
  245. responseType: "blob",
  246. onload: function (e) {
  247. let blob = window.URL.createObjectURL(e.response);
  248. let a = document.createElement("a");
  249. a.href = blob;
  250. a.download = e.finalUrl.match(/[0-9]+_ugoira/g)[0] + ".zip";
  251. a.click();
  252. }
  253. });
  254. });
  255. }.bind(this)).catch(function(err) {
  256. console.log("抓取動圖資料發生問題:", err);
  257. })
  258. }
  259. // [設定標籤]
  260. previewbox.prototype._addTags = function(tags) {
  261. let R18 = "";
  262. let nR18 = "";
  263. for(let i = 0; i < tags.length; i++) {
  264. if(tags[i].tag.match(/R-18G|R-18|R18|R18G/g)) {
  265. R18 += `${tags[i].tag} `;
  266. } else {
  267. nR18 += `${tags[i].tag} `;
  268. }
  269. }
  270. this.box.querySelector(".tags").innerHTML = `<span style='color: tomato'>${R18}</span>${nR18}`;
  271. }
  272. // [取得圖片ID]
  273. previewbox.prototype._getId = function() {
  274. if (this.target.nodeName == "A") {
  275. // return /illust_id=([0-9]+)/g.exec(this.target.href)[1];
  276. return /([0-9]+)/g.exec(this.target.href)[1];
  277. } else if (this.target.nodeName == "IMG") {
  278. return /\/img\/.+\/([0-9]+)/g.exec(this.target.src)[1];
  279. } else if (this.target.nodeName == "DIV") {
  280. return /\/img\/.+\/([0-9]+)/g.exec(this.target.style.backgroundImage)[1];
  281. }
  282. }
  283. // [取得圖片連結]
  284. previewbox.prototype._getImgurls = function(url, count) {
  285. let urls = [];
  286. let urls_o = [];
  287. let regexp = new RegExp("(\/img\/.+" + this.pid + ")", "g");
  288. let img_date_url = url.match(regexp)[0];
  289. for(let i = 0; i < count; i++) {
  290. urls.push(`https://i.pximg.net/c/600x600/img-master${img_date_url}_p${i}_master1200.jpg`);
  291. urls_o.push(url.replace(/p0/, `p${i}`));
  292. }
  293. return [`https://www.pixiv.net/member_illust.php?mode=medium&illust_id=${this.pid}`, urls, urls_o];
  294. }
  295. // [抓取圖片資料]
  296. // https://www.pixiv.net/ajax/illust/:illustId
  297. // https://www.pixiv.net/ajax/illust/:illustId/pages
  298. // https://www.pixiv.net/ajax/tags/illust/:illustId
  299. // https://www.pixiv.net/ajax/user/:userId
  300. previewbox.prototype._getData = function(fun) {
  301. fetch(`https://www.pixiv.net/ajax/illust/${this.pid}`).then(function(response) {
  302. if (!response.ok) throw new Error(response.statusText);
  303. return response.json();
  304. }).then(fun).catch(function(err) {
  305. console.log("抓取圖片資料發生問題:", err)
  306. })
  307. }
  308.  
  309. // ========== pixiv zipplayer ========== //
  310. function ZipImagePlayer(options) {
  311. this._Uint8Array = (window.Uint8Array || window.WebKitUint8Array || window.MozUint8Array || window.MSUint8Array);
  312. this._DataView = (window.DataView || window.WebKitDataView || window.MozDataView || window.MSDataView);
  313. this._ArrayBuffer = (window.ArrayBuffer || window.WebKitArrayBuffer || window.MozArrayBuffer || window.MSArrayBuffer);
  314. this.op = options;
  315. this._loadingState = 0;
  316. this._context = options.canvas.getContext("2d");
  317. this._files = {};
  318. this._frameCount = this.op.metadata.frames.length;
  319. this._frame = 0;
  320. this._loadFrame = 0;
  321. this._frameImages = [];
  322. this._paused = false;
  323. this._loadTimer = null;
  324. this._startLoad();
  325. this._loadXhr = null;
  326. }
  327. ZipImagePlayer.prototype = {
  328. _trailerBytes: 30000,
  329. _load: function(offset, length, callback) {
  330. var _this = this;
  331. var xhr = new XMLHttpRequest();
  332. xhr.addEventListener("load", function(ev) {
  333. if (!zp) return;
  334. if (xhr.status == 200) {
  335. offset = 0;
  336. length = xhr.response.byteLength;
  337. _this._len = length;
  338. _this._buf = xhr.response;
  339. _this._bytes = new _this._Uint8Array(_this._buf);
  340. } else {
  341. _this._bytes.set(new _this._Uint8Array(xhr.response), offset);
  342. }
  343. if (callback) {
  344. callback.apply(_this, [offset, length]);
  345. }
  346. }, false);
  347. xhr.open("GET", this.op.source);
  348. xhr.responseType = "arraybuffer";
  349. if (offset != null && length != null) {
  350. var end = offset + length;
  351. xhr.setRequestHeader("Range", "bytes=" + offset + "-" + (end - 1));
  352. if (this._isSafari) {
  353. xhr.setRequestHeader("Cache-control", "no-cache");
  354. xhr.setRequestHeader("If-None-Match", Math.random().toString());
  355. }
  356. }
  357. this._loadXhr = xhr;
  358. this._loadXhr.send();
  359. },
  360. _startLoad: function() {
  361. var _this = this;
  362. var http = new XMLHttpRequest();
  363. http.open('HEAD', this.op.source);
  364. http.onreadystatechange = function() {
  365. if (this.readyState == this.DONE) {
  366. _this._pHead = 0;
  367. _this._pNextHead = 0;
  368. _this._pFetch = 0;
  369. var len = parseInt(this.getResponseHeader("Content-Length"));
  370. _this._len = len;
  371. _this._buf = new _this._ArrayBuffer(len);
  372. _this._bytes = new _this._Uint8Array(_this._buf);
  373. var off = len - _this._trailerBytes;
  374. if (off < 0) {
  375. off = 0;
  376. }
  377. _this._pTail = len;
  378. _this._load(off, len - off, function(off, len) {
  379. _this._pTail = off;
  380. _this._findCentralDirectory();
  381. });
  382. }
  383. };
  384. http.send();
  385. },
  386. _findCentralDirectory: function() {
  387. var dv = new this._DataView(this._buf, this._len - 22, 22);
  388. var cd_count = dv.getUint16(10, true);
  389. var cd_size = dv.getUint32(12, true);
  390. var cd_off = dv.getUint32(16, true);
  391. if (cd_off < this._pTail) {
  392. this._load(cd_off, this._pTail - cd_off, function() {
  393. this._pTail = cd_off;
  394. this._readCentralDirectory(cd_off, cd_size, cd_count);
  395. });
  396. } else {
  397. this._readCentralDirectory(cd_off, cd_size, cd_count);
  398. }
  399. },
  400. _readCentralDirectory: function(offset, size, count) {
  401. var dv = new this._DataView(this._buf, offset, size);
  402. var p = 0;
  403. for (var i = 0; i < count; i++ ) {
  404. var compMethod = dv.getUint16(p + 10, true);
  405. var uncompSize = dv.getUint32(p + 24, true);
  406. var nameLen = dv.getUint16(p + 28, true);
  407. var extraLen = dv.getUint16(p + 30, true);
  408. var cmtLen = dv.getUint16(p + 32, true);
  409. var off = dv.getUint32(p + 42, true);
  410. p += 46;
  411. var nameView = new this._Uint8Array(this._buf, offset + p, nameLen);
  412. var name = "";
  413. for (var j = 0; j < nameLen; j++) {
  414. name += String.fromCharCode(nameView[j]);
  415. }
  416. p += nameLen + extraLen + cmtLen;
  417. this._files[name] = {off: off, len: uncompSize};
  418. }
  419. if (this._pHead >= this._pTail) {
  420. this._pHead = this._len;
  421. document.dispatchEvent(new CustomEvent("loadProgress", {detail: this._pHead / this._len}));
  422. this._loadNextFrame();
  423. } else {
  424. this._loadNextChunk();
  425. this._loadNextChunk();
  426. }
  427. },
  428. _loadNextChunk: function() {
  429. if (this._pFetch >= this._pTail) {
  430. return;
  431. }
  432. var off = this._pFetch;
  433. var len = this.op.chunkSize;
  434. if (this._pFetch + len > this._pTail) {
  435. len = this._pTail - this._pFetch;
  436. }
  437. this._pFetch += len;
  438. this._load(off, len, function() {
  439. if (off == this._pHead) {
  440. if (this._pNextHead) {
  441. this._pHead = this._pNextHead;
  442. this._pNextHead = 0;
  443. } else {
  444. this._pHead = off + len;
  445. }
  446. if (this._pHead >= this._pTail) {
  447. this._pHead = this._len;
  448. }
  449. document.dispatchEvent(new CustomEvent("loadProgress", {detail: this._pHead / this._len}));
  450. if (!this._loadTimer) {
  451. this._loadNextFrame();
  452. }
  453. } else {
  454. this._pNextHead = off + len;
  455. }
  456. this._loadNextChunk();
  457. });
  458. },
  459. _fileDataStart: function(offset) {
  460. var dv = new DataView(this._buf, offset, 30);
  461. var nameLen = dv.getUint16(26, true);
  462. var extraLen = dv.getUint16(28, true);
  463. return offset + 30 + nameLen + extraLen;
  464. },
  465. _isFileAvailable: function(name) {
  466. var info = this._files[name];
  467. if (!info) {
  468. this._error("File " + name + " not found in ZIP");
  469. }
  470. if (this._pHead < (info.off + 30)) {
  471. return false;
  472. }
  473. return this._pHead >= (this._fileDataStart(info.off) + info.len);
  474. },
  475. _loadNextFrame: function() {
  476. if (this._dead) {
  477. return;
  478. }
  479. var frame = this._loadFrame;
  480. if (frame >= this._frameCount) {
  481. return;
  482. }
  483. var meta = this.op.metadata.frames[frame];
  484. if (!this.op.source) {
  485. // Unpacked mode (individiual frame URLs)
  486. this._loadFrame += 1;
  487. this._loadImage(frame, meta.file, false);
  488. return;
  489. }
  490. if (!this._isFileAvailable(meta.file)) {
  491. return;
  492. }
  493. this._loadFrame += 1;
  494. var off = this._fileDataStart(this._files[meta.file].off);
  495. var end = off + this._files[meta.file].len;
  496. var url;
  497. var mime_type = this.op.metadata.mime_type || "image/png";
  498. if (this._URL) {
  499. var slice;
  500. if (!this._buf.slice) {
  501. slice = new this._ArrayBuffer(this._files[meta.file].len);
  502. var view = new this._Uint8Array(slice);
  503. view.set(this._bytes.subarray(off, end));
  504. } else {
  505. slice = this._buf.slice(off, end);
  506. }
  507. var blob;
  508. try {
  509. blob = new this._Blob([slice], {type: mime_type});
  510. }
  511. catch (err) {
  512. var bb = new this._BlobBuilder();
  513. bb.append(slice);
  514. blob = bb.getBlob();
  515. }
  516. url = this._URL.createObjectURL(blob);
  517. this._loadImage(frame, url, true);
  518. } else {
  519. url = ("data:" + mime_type + ";base64,"
  520. + base64ArrayBuffer(this._buf, off, end - off));
  521. this._loadImage(frame, url, false);
  522. }
  523. },
  524. _loadImage: function(frame, url, isBlob) {
  525. var _this = this;
  526. var image = new Image();
  527. var meta = this.op.metadata.frames[frame];
  528. image.addEventListener('load', function() {
  529. if (isBlob) {
  530. _this._URL.revokeObjectURL(url);
  531. }
  532. if (_this._dead) {
  533. return;
  534. }
  535. _this._frameImages[frame] = image;
  536. document.dispatchEvent(new CustomEvent("frameLoaded", [frame]));
  537. if (_this._loadingState == 0) {
  538. _this._displayFrame.apply(_this);
  539. }
  540. if (frame >= (_this._frameCount - 1)) {
  541. _this._setLoadingState(2);
  542. _this._buf = null;
  543. _this._bytes = null;
  544. } else {
  545. if (!_this._maxLoadAhead ||
  546. (frame - _this._frame) < _this._maxLoadAhead) {
  547. _this._loadNextFrame();
  548. } else if (!_this._loadTimer) {
  549. _this._loadTimer = setTimeout(function() {
  550. _this._loadTimer = null;
  551. _this._loadNextFrame();
  552. }, 200);
  553. }
  554. }
  555. });
  556. image.src = url;
  557. },
  558. _setLoadingState: function(state) {
  559. if (this._loadingState != state) {
  560. this._loadingState = state;
  561. document.dispatchEvent(new CustomEvent("loadingStateChanged", [state]));
  562. }
  563. },
  564. _displayFrame: function() {
  565. if (this._dead) {
  566. return;
  567. }
  568. var _this = this;
  569. var meta = this.op.metadata.frames[this._frame];
  570. var image = this._frameImages[this._frame];
  571. if (!image) {
  572. this._setLoadingState(0);
  573. return;
  574. }
  575. if (this._loadingState != 2) {
  576. this._setLoadingState(1);
  577. }
  578. if (this.op.autosize) {
  579. if (this._context.canvas.width != image.width || this._context.canvas.height != image.height) {
  580. // make the canvas autosize itself according to the images drawn on it
  581. // should set it once, since we don't have variable sized frames
  582. this._context.canvas.width = image.width;
  583. this._context.canvas.height = image.height;
  584. }
  585. };
  586. this._context.clearRect(0, 0, this.op.canvas.width,
  587. this.op.canvas.height);
  588. this._context.drawImage(image, 0, 0);
  589. document.dispatchEvent(new CustomEvent("frame", { detail: { frame: this._frame, count: this._frameCount } }));
  590. if (!this._paused) {
  591. this._timer = setTimeout(function() {
  592. _this._timer = null;
  593. _this._nextFrame.apply(_this);
  594. }, meta.delay);
  595. }
  596. },
  597. _nextFrame: function(frame) {
  598. if (this._frame >= (this._frameCount - 1)) {
  599. if (this.op.loop) {
  600. this._frame = 0;
  601. } else {
  602. this.pause();
  603. return;
  604. }
  605. } else {
  606. this._frame += 1;
  607. }
  608. this._displayFrame();
  609. },
  610. play: function() {
  611. if (this._dead) {
  612. return;
  613. }
  614. if (this._paused) {
  615. document.dispatchEvent(new CustomEvent("play", [this._frame]));
  616. this._paused = false;
  617. this._displayFrame();
  618. }
  619. },
  620. getCurrentFrame: function() {
  621. return this._frame;
  622. },
  623. getLoadedFrames: function() {
  624. return this._frameImages.length;
  625. },
  626. getFrameCount: function() {
  627. return this._frameCount;
  628. },
  629. loadAbort: function() {
  630. this._loadXhr.abort()
  631. this._loadXhr = null;
  632. }
  633. }
  634. function base64ArrayBuffer(arrayBuffer, off, byteLength) {
  635. var base64 = '';
  636. var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  637. var bytes = new Uint8Array(arrayBuffer);
  638. var byteRemainder = byteLength % 3;
  639. var mainLength = off + byteLength - byteRemainder;
  640. var a, b, c, d;
  641. var chunk;
  642. // Main loop deals with bytes in chunks of 3
  643. for (var i = off; i < mainLength; i = i + 3) {
  644. // Combine the three bytes into a single integer
  645. chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
  646. // Use bitmasks to extract 6-bit segments from the triplet
  647. a = (chunk & 16515072) >> 18; // 16515072 = (2^6 - 1) << 18
  648. b = (chunk & 258048) >> 12; // 258048 = (2^6 - 1) << 12
  649. c = (chunk & 4032) >> 6; // 4032 = (2^6 - 1) << 6
  650. d = chunk & 63; // 63 = 2^6 - 1
  651. // Convert the raw binary segments to the appropriate ASCII encoding
  652. base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
  653. }
  654. // Deal with the remaining bytes and padding
  655. if (byteRemainder == 1) {
  656. chunk = bytes[mainLength];
  657. a = (chunk & 252) >> 2; // 252 = (2^6 - 1) << 2
  658. // Set the 4 least significant bits to zero
  659. b = (chunk & 3) << 4; // 3 = 2^2 - 1
  660. base64 += encodings[a] + encodings[b] + '==';
  661. } else if (byteRemainder == 2) {
  662. chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1];
  663. a = (chunk & 64512) >> 10; // 64512 = (2^6 - 1) << 10
  664. b = (chunk & 1008) >> 4; // 1008 = (2^6 - 1) << 4
  665. // Set the 2 least significant bits to zero
  666. c = (chunk & 15) << 2; // 15 = 2^4 - 1
  667. base64 += encodings[a] + encodings[b] + encodings[c] + '=';
  668. }
  669. return base64;
  670. }
  671. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址