img-viewer

img-viewer is a script for better picture-viewing experience.

  1. // ==UserScript==
  2. // @name img-viewer
  3. // @namespace https://sharils.github.io/gist/
  4. // @description img-viewer is a script for better picture-viewing experience.
  5. // @match <all_urls>
  6. // @version 1.0.0
  7. // @grant none
  8. // @noframes
  9. // ==/UserScript==
  10. (function(global) {
  11. "use strict";
  12.  
  13. var Images = global.Sharils.Images = function() {};
  14.  
  15. Images.prototype.scrollToNext = function() {
  16. var maybeImage,
  17. imageRect,
  18. viewportHeight;
  19.  
  20. maybeImage = this._next();
  21. if (!maybeImage) {
  22. return null;
  23. }
  24.  
  25. imageRect = maybeImage.getBoundingClientRect();
  26. viewportHeight = this._viewportHeight();
  27. if (maybeImage.height <= viewportHeight || 0 < maybeImage.top) {
  28. maybeImage.scrollIntoView();
  29. } else if (imageRect.bottom <= viewportHeight * 2) {
  30. global.scrollBy(0, imageRect.bottom - viewportHeight);
  31. } else {
  32. global.scrollBy(0, viewportHeight);
  33.  
  34. }
  35.  
  36. return maybeImage;
  37. };
  38.  
  39. Images.prototype.scrollToPrevious = function() {
  40. var maybeImage;
  41.  
  42. maybeImage = this._previous();
  43.  
  44. if (maybeImage) {
  45. maybeImage.scrollIntoView();
  46. }
  47.  
  48. return maybeImage;
  49. };
  50.  
  51. Images.prototype._above = function(position, length) {
  52. return function(image) {
  53. return image.getBoundingClientRect()[position] <= length;
  54. };
  55. };
  56.  
  57. Images.prototype._below = function(position, length) {
  58. return function(image) {
  59. return length <= image.getBoundingClientRect()[position];
  60. };
  61. };
  62.  
  63. Images.prototype._compare = function(position, low, high) {
  64. return low.getBoundingClientRect()[position] -
  65. high.getBoundingClientRect()[position];
  66. };
  67.  
  68. Images.prototype._documentImages = function() {
  69. return [].slice.call(global.document.images);
  70. };
  71.  
  72. Images.prototype._max = function(position) {
  73. return function(low, high) {
  74. return this._compare(position, low, high) >= 0 ? low : high;
  75. }.bind(this);
  76. };
  77.  
  78. Images.prototype._min = function(position) {
  79. return function(low, high) {
  80. return this._compare(position, low, high) <= 0 ? low : high;
  81. }.bind(this);
  82. };
  83.  
  84. Images.prototype._next = function() {
  85. var images,
  86. maybeImage,
  87. partInViewportBelow,
  88. viewportBelowTop;
  89.  
  90. viewportBelowTop = this._viewportHeight() + 1;
  91.  
  92. partInViewportBelow = this._below("bottom", viewportBelowTop);
  93.  
  94. images = this._documentImages();
  95. images = images.filter(partInViewportBelow);
  96. images = images.filter(this._visible);
  97. images = images.filter(this._notFixed);
  98. maybeImage = images.length ? images.reduce(this._min("top")) : null;
  99.  
  100. return maybeImage;
  101. };
  102.  
  103. Images.prototype._notFixed = function(image) {
  104. var imageTop,
  105. notFixed;
  106.  
  107. imageTop = image.getBoundingClientRect().top;
  108.  
  109. global.scrollBy(0, 1);
  110.  
  111. notFixed = imageTop !== image.getBoundingClientRect().top;
  112.  
  113. global.scrollBy(0, -1);
  114.  
  115. return notFixed;
  116. };
  117.  
  118. Images.prototype._previous = function() {
  119. var image,
  120. imageRect,
  121. images,
  122. inViewportAbove,
  123. maybeImage,
  124. partInViewportAbove,
  125. viewportAboveTop;
  126.  
  127. partInViewportAbove = this._above("top", -1);
  128.  
  129. images = this._documentImages();
  130. images = images.filter(partInViewportAbove);
  131. images = images.filter(this._visible);
  132. images = images.filter(this._notFixed);
  133. if (!images.length) {
  134. return null;
  135. }
  136.  
  137. image = images.reduce(this._max("bottom"));
  138. imageRect = image.getBoundingClientRect();
  139. viewportAboveTop = imageRect.bottom - this._viewportHeight();
  140. if (imageRect.top <= viewportAboveTop) {
  141. return image;
  142. }
  143.  
  144. inViewportAbove = this._below("top", viewportAboveTop);
  145. images = images.filter(inViewportAbove);
  146. maybeImage = images.length ? images.reduce(this._min("top")) : null;
  147.  
  148. return maybeImage;
  149. };
  150.  
  151. Images.prototype._viewportHeight = function() {
  152. return global.innerHeight;
  153. };
  154.  
  155. Images.prototype._visible = function(image) {
  156. return 0 < image.getBoundingClientRect().height;
  157. };
  158. })(this, this.Sharils = this.Sharils || {});
  159. (function(global) {
  160. "use strict";
  161.  
  162. var Controller = global.Sharils.Images.Controller = function(images) {
  163. this._onClick = this._onClick.bind(this);
  164.  
  165. this._onImageRightClick = this._onLowerIPress =
  166. images.scrollToNext.bind(images);
  167.  
  168. this._onImageLeftClick = this._onUpperIPress =
  169. images.scrollToPrevious.bind(images);
  170.  
  171. this._onKeyPress = this._onKeyPress.bind(this);
  172. };
  173.  
  174. Controller.prototype.addEventListeners = function() {
  175. global.addEventListener("click", this._onClick);
  176. global.addEventListener("keypress", this._onKeyPress);
  177. };
  178.  
  179. Controller.prototype.removeEventListeners = function() {
  180. global.removeEventListener("click", this._onClick);
  181. global.removeEventListener("keypress", this._onKeyPress);
  182. };
  183.  
  184. Controller.prototype._onClick = function(e) {
  185. var imageRect;
  186.  
  187. if (e.target.nodeName !== "IMG") {
  188. return;
  189. }
  190.  
  191. imageRect = e.target.getBoundingClientRect();
  192. if (e.screenX < imageRect.left + imageRect.width / 2) {
  193. this._onImageLeftClick();
  194. } else {
  195. this._onImageRightClick();
  196. }
  197. };
  198.  
  199. Controller.prototype._onImageLeftClick = function() {
  200. // no operation is intended
  201. };
  202.  
  203. Controller.prototype._onImageRightClick = function() {
  204. // no operation is intended
  205. };
  206.  
  207. Controller.prototype._onKeyPress = function(e) {
  208. switch(e.charCode)
  209. {
  210. case 73:
  211. this._onUpperIPress();
  212. break;
  213. case 105:
  214. this._onLowerIPress();
  215. break;
  216. }
  217. };
  218.  
  219. Controller.prototype._onLowerIPress = function() {
  220. // no operation is intended
  221. };
  222.  
  223. Controller.prototype._onUpperIPress = function() {
  224. // no operation is intended
  225. };
  226. })(this);
  227. if (top === window) {
  228. new Sharils.Images.Controller(new Sharils.Images).addEventListeners();
  229. }