yoedge-horizontal-screen

实现灰机汉化组漫画网站(yoedge.com)的横屏阅读模式

目前為 2017-09-17 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name yoedge-horizontal-screen
  3. // @namespace https://github.com/Lockvictor
  4. // @author Lockvictor
  5. // @description 实现灰机汉化组漫画网站(yoedge.com)的横屏阅读模式
  6. // @homepage https://github.com/Lockvictor/yoedge-horizontal-screen
  7. // @match http://*.yoedge.com/smp-app/*
  8. // @version 2.1.0
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. //漫画宽高比
  13. const MANGA_ASPECT_RATIO = 1.5;
  14.  
  15. //默认屏幕占比
  16. const DEFAULT_SCALE_RATIO = 0.6;
  17. //最大占比,漫画与屏幕等宽
  18. const MAX_SCALE_RATIO = 1;
  19. //最小占比,漫画与屏幕等高,即原来的竖屏模式
  20. const MIN_SCALE_RATIO = window.screen.height / MANGA_ASPECT_RATIO / window.screen.width;
  21. //屏幕占比缩放步长
  22. const SCALE_STEP = 0.05;
  23.  
  24. //j和k快捷键滚动屏幕的比例
  25. const SCROLLBY_RATIO = 0.2;
  26.  
  27. //漫画当前屏幕占比
  28. let gMangaAreaRatio = DEFAULT_SCALE_RATIO;
  29.  
  30. (function () {
  31. 'use strict';
  32.  
  33. document.body.innerHTML = '';
  34. document.addEventListener('contextmenu', event => event.preventDefault());
  35.  
  36. //获取本话的配置信息,主要包括页数和图片的url
  37. let configUrl = window.location.href.replace('index.html', 'smp_cfg.json');
  38. fetch(configUrl).then(response => response.json())
  39. .then(config => loadAllPage(config))
  40. .catch(e => alert(e));
  41. })();
  42.  
  43. function loadAllPage(config) {
  44. //获取图片url和序号
  45. let orderList = config.pages.order;
  46. let imageUrlList = config.pages.page;
  47.  
  48. //创建漫画原图的img元素
  49. for (let i = 0; i < orderList.length; i++) {
  50. let order = orderList[i];
  51. let img = document.createElement('img');
  52. img.id = 'img' + order;
  53. img.style.display = 'none';
  54. img.src = imageUrlList[order];
  55. document.body.appendChild(img);
  56. }
  57.  
  58. let imgElementList = document.getElementsByTagName('img');
  59.  
  60. //图片传输需要时间,直接获取会出错,采用分次加载,每次加载5张图
  61. let loadStartIndex = 0;
  62. const LOAD_STEP = 5;
  63. const INDEX_LIMIT = imgElementList.length;
  64. // console.log(`INDEX_LIMIT: ${INDEX_LIMIT}`);
  65. let imgSizeCheckFlag = setInterval(function () {
  66. let expectEndIndex = loadStartIndex + LOAD_STEP;
  67. let loadEndIndex = (expectEndIndex > INDEX_LIMIT)? INDEX_LIMIT : expectEndIndex;
  68.  
  69. if (isImageOk(imgElementList, loadStartIndex, loadEndIndex)) {
  70. //用canvas绘制原图,实现反色块的处理
  71. drawImageByCanvas(orderList, imageUrlList, loadStartIndex, loadEndIndex);
  72. loadStartIndex = loadEndIndex;
  73. }
  74. if (loadStartIndex === INDEX_LIMIT) {
  75. clearInterval(imgSizeCheckFlag);
  76. }
  77. // console.log('loadStartIndex: ' + loadStartIndex);
  78. }, 1000);
  79.  
  80. // 自定义缩放、滚动等快捷键
  81. customizeShortcut();
  82. }
  83.  
  84. function isImageOk(imgElementList, startIndex, endIndex) {
  85. let isLoaded = true;
  86. for (let i = startIndex; i < endIndex; i++) {
  87. let img = imgElementList[i];
  88. if (!img.complete || img.naturalWidth === 0) {
  89. isLoaded = false;
  90. break;
  91. }
  92. }
  93. return isLoaded;
  94. }
  95.  
  96. function drawImageByCanvas(orderList, imageUrlList, startIndex, endIndex) {
  97. for (let i = startIndex; i < endIndex; i++) {
  98. let order = orderList[i];
  99.  
  100. let img = document.getElementById('img' + order);
  101. let width = img.naturalWidth;
  102. let height = img.naturalHeight;
  103. // console.log(img);
  104. // console.log(`order: ${order}, width: ${width}, height: ${height}`);
  105.  
  106. let mgcanv = document.createElement('canvas');
  107. mgcanv.id = 'canvas' + order;
  108. mgcanv.width = width;
  109. mgcanv.height = height;
  110. mgcanv.style.display = 'block';
  111. mgcanv.style.margin = '0 auto';
  112. mgcanv.style.width = numberToPercentage(gMangaAreaRatio);
  113. // console.log(mgcanv);
  114.  
  115. let context = mgcanv.getContext('2d');
  116. context.drawImage(img, 0, 0);
  117. //前5张图没有反色块
  118. if (i > 4) {
  119. cleanClutter(imageUrlList[order], context, width, height);
  120. }
  121. document.body.appendChild(mgcanv);
  122. }
  123. }
  124.  
  125. function cleanClutter(imageUrl, context, width, height) {
  126. var imageData = context.getImageData(0, 0, width, height);
  127. var data = imageData.data;
  128.  
  129. var j1 = 0, j2 = 0;
  130. var r = width * 4, r1 = r * 110, r2 = r;
  131. var y1 = Math.floor(height >> 1) & 0xFFFFFFE;
  132. if (window.clutter == '2' && imageUrl.indexOf('.png') !== -1) {
  133. y1 = 100;
  134. }
  135. var x1 = Math.floor(width / 3);
  136. var x2 = x1 << 1;
  137. var y2 = y1 + 20;
  138. if (height > 550) {
  139. y2 = y1 + 60;
  140. }
  141.  
  142. if (window.clutter == '2') {
  143. var lastOf = imageUrl.lastIndexOf('/');
  144. var fileName = decodeURI(imageUrl.substring(lastOf));
  145. if (fileName.indexOf("3") != -1) {
  146. y1 -= 8;
  147. y2 -= 11;
  148. } else if (fileName.indexOf("4") != -1) {
  149. y1 -= 6;
  150. y2 -= 13;
  151. } else if (fileName.indexOf("5") != -1) {
  152. y2 -= 14;
  153. } else if (fileName.indexOf("7") != -1) {
  154. y1 -= 2;
  155. y2 -= 3;
  156. } else if (fileName.indexOf("9") != -1) {
  157. y2 -= 13;
  158. } else if (fileName.indexOf("01") != -1) {
  159. y2 -= 11;
  160. } else if (fileName.indexOf("02") != -1) {
  161. y1 -= 12;
  162. y2 -= 17;
  163. }
  164. if (fileName.indexOf("4") != -1) {
  165. x1 -= 7;
  166. x2 -= 19;
  167. } else if (fileName.indexOf("5") != -1) {
  168. x1 -= 15;
  169. x2 -= 13;
  170. } else if (fileName.indexOf("6") != -1) {
  171. x1 -= 31;
  172. x2 = x1 + 76;
  173. } else if (fileName.indexOf("7") != -1) {
  174. x1 += 21;
  175. } else if (fileName.indexOf("8") != -1) {
  176. x2 -= 13;
  177. } else if (fileName.indexOf("01") != -1) {
  178. x2 = x1 + 99;
  179. } else if (fileName.indexOf("02") != -1) {
  180. x1 -= 13;
  181. x2 = x1 + 97;
  182. } else if (fileName.indexOf("03") != -1) {
  183. x2 = x1 + 91;
  184. }
  185. }
  186. var is = y1 * r + x1 * 4, i = 0;
  187.  
  188. if (window.clutter == '2') {
  189. for (j1 = y1; j1 < y2; j1 += 1) {
  190. i = is;
  191. for (j2 = x1; j2 < x2; j2++) {
  192. data[i + 2] = data[i + 2] ^ 0xFF;
  193. data[i + 1] = data[i + 1] ^ 0xFF;
  194. data[i] = data[i] ^ 0xFF;
  195. i += 4;
  196. }
  197. x2--;
  198. is += r2;
  199. }
  200. } else {
  201. for (j1 = y1; j1 < y2; j1 += 1) {
  202. i = is;
  203. for (j2 = x1; j2 < x2; j2++) {
  204. if (data[i + 3 - r1] !== 0) {
  205. data[i + 2] = data[i + 2] ^ data[i + 2 - r1];
  206. data[i + 1] = data[i + 1] ^ data[i + 1 - r1];
  207. data[i] = data[i] ^ data[i - r1];
  208. }
  209. i += 4;
  210. }
  211. is += r2;
  212. }
  213. }
  214.  
  215. context.putImageData(imageData, 0, 0);
  216. }
  217.  
  218. function scale(increment) {
  219. var expectedScaleRatio = gMangaAreaRatio + increment;
  220. if (increment > 0) {
  221. gMangaAreaRatio = (expectedScaleRatio >= MAX_SCALE_RATIO) ? MAX_SCALE_RATIO : expectedScaleRatio;
  222. } else if (increment < 0) {
  223. gMangaAreaRatio = (expectedScaleRatio <= MIN_SCALE_RATIO) ? MIN_SCALE_RATIO : expectedScaleRatio;
  224. }
  225.  
  226. var newWidth = numberToPercentage(gMangaAreaRatio);
  227. canvasList = document.getElementsByTagName('canvas');
  228. for (var i = 0; i < canvasList.length; i++) {
  229. canvasList[i].style.width = newWidth;
  230. }
  231. }
  232.  
  233. function customizeShortcut() {
  234. // 漫画缩放、滚动、翻页
  235. // 缩放和滚动都可以持续响应,注册到keydown
  236. document.addEventListener('keydown', function (event) {
  237. switch (event.key) {
  238. case '=':
  239. scale(SCALE_STEP);
  240. break;
  241. case '-':
  242. scale(-SCALE_STEP);
  243. break;
  244. case '0':
  245. gMangaAreaRatio = DEFAULT_SCALE_RATIO;
  246. scale(0);
  247. break;
  248. case 'j':
  249. smoothyScrollBy(0, screen.width * gMangaAreaRatio * MANGA_ASPECT_RATIO * SCROLLBY_RATIO);
  250. break;
  251. case 'k':
  252. smoothyScrollBy(0, -screen.width * gMangaAreaRatio * MANGA_ASPECT_RATIO * SCROLLBY_RATIO);
  253. break;
  254. case 'h':
  255. smoothyScrollTo(0, 0);
  256. break;
  257. case 'l':
  258. smoothyScrollTo(0, document.body.scrollHeight);
  259. break;
  260. default:
  261. break;
  262. }
  263. });
  264.  
  265. // 跳转下一话是单次响应,注册到keyup
  266. document.addEventListener('keyup', function (event) {
  267. switch (event.key) {
  268. case 'n':
  269. smp.toolbar.nextApp();
  270. break;
  271. default:
  272. break;
  273. }
  274. });
  275. }
  276.  
  277.  
  278. //=====================基础功能相关函数=====================
  279. function smoothyScrollBy(offsetX, offsetY) {
  280. try {
  281. window.scrollBy({ top: offsetY, left: offsetX, behavior: 'smooth' });
  282. } catch (error) {
  283. window.scrollBy(offsetX, offsetY);
  284. }
  285. }
  286.  
  287. function smoothyScrollTo(x, y) {
  288. try {
  289. window.scrollTo({ top: y, left: x, behavior: 'smooth' });
  290. } catch (error) {
  291. window.scrollTo(x, y);
  292. }
  293. }
  294.  
  295. function numberToPercentage(value) {
  296. return Math.floor(100 * value) + '%';
  297. }