4chan Image Browser

Opens current thread Images in 4chan into a popup viewer, tested in Tampermonkey

目前為 2014-09-19 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name 4chan Image Browser
  3. // @namespace IdontKnowWhatToDoWithThis
  4. // @description Opens current thread Images in 4chan into a popup viewer, tested in Tampermonkey
  5. // @match *://*.4chan.org/*/res/*
  6. // @match *://*.4chan.org/*/thread/*
  7. // @version 6.2
  8. // @copyright 2014+, Gyst
  9. // @grant unsafeWindow
  10. // ==/UserScript==
  11.  
  12.  
  13. /**
  14. * Constructor function, the outer function is run immediately to store the
  15. *the constants in a closure
  16. */
  17. var Viewer = (function(){
  18. var INDEX_KEY = "imageBrowserIndexCookie";
  19. var THREAD_KEY="imageBrowserThreadCookie";
  20. var WIDTH_KEY = "imageBrowserWidthCookie";
  21. //cookieInfo
  22. var HEIGHT_KEY = "imageBrowserHeightCookie";
  23.  
  24. //IDs for important elements
  25. var VIEW_ID = "mainView";
  26. var IMG_ID = "mainImg";
  27. var IMG_BOX_ID = "imageBox";
  28. var TOP_LAYER_ID = "viewerTopLayer";
  29. var IMG_WRAPPER_ID = 'mainImgWrapper';
  30. //styles for added elements
  31. var STYLE_TEXT='\
  32. div.reply.highlight{z-index:100 !important;position:fixed !important; top:1%;left:1%;}\
  33. body{overflow:hidden !important;}\
  34. #quote-preview{z-index:100;} \
  35. a.quotelink, div.viewerBacklinks a.quotelink{color:#5c5cff !important;}\
  36. a.quotelink:hover, div.viewerBacklinks a:hover{color:red !important;}\
  37. #'+IMG_ID+'{display:block !important; margin:auto;max-width:100%;height:auto;-webkit-user-select: none;cursor:pointer;}\
  38. #'+VIEW_ID+'{\
  39. background-color:rgba(0,0,0,0.9);\
  40. z-index:10; \
  41. position:fixed; \
  42. top:0;left:0;bottom:0;right:0; \
  43. overflow:auto;\
  44. text-align:center;\
  45. -webkit-user-select: none;\
  46. }\
  47. #'+IMG_BOX_ID+' {display:flex;align-items:center;justify-content:center;flex-direction: column;min-height:100%;}\
  48. #'+IMG_WRAPPER_ID+' {width:100%;}\
  49. #'+TOP_LAYER_ID+'{position:fixed;top:0;bottom:0;left:0;right:0;z-index:20;opacity:0;visibility:hidden;transition:all .25s ease;}\
  50. .viewerBlockQuote{color:white;}\
  51. #viewerTextWrapper{max-width:60em;display:inline-block; color:gray;-webkit-user-select: all;}\
  52. .bottomMenuShow{visibility:visible;}\
  53. #viewerBottomMenu{box-shadow: -1px -1px 5px #888888;font-size:20px;padding:5px;background-color:white;position:fixed;bottom:0;right:0;z-index:200;}\
  54. .hideCursor{cursor:none !important;}\
  55. .hidden{visibility:hidden}\
  56. .displayNone{display:none;}\
  57. .pagingButtons{font-size:100px;color:white;text-shadow: 1px 1px 10px #27E3EB;z-index: 11;top: 50%;position: absolute;margin-top: -57px;width:100px;cursor:pointer;-webkit-user-select: none;}\
  58. .pagingButtons:hover{color:#27E3EB;text-shadow: 1px 1px 10px #000}\
  59. #previousImageButton{left:0;text-align:left;}\
  60. #nextImageButton{right:0;text-align:right;}\
  61. @-webkit-keyframes flashAnimation{0%{ text-shadow: none;}100%{text-shadow: 0px 0px 5px lightblue;}}\
  62. .flash{-webkit-animation: flashAnimation .5s alternate infinite linear;}\
  63. ';
  64. //the real constructor
  65. return function(){
  66. //for holding img srcs and a pointer for traversing
  67. this.postData = [];
  68. this.linkIndex = 0;
  69.  
  70. //set up the div and image for the popup
  71. this.mainView = null;
  72. this.mainImg = null;
  73. this.centerBox = null;
  74. this.topLayer = null;
  75. this.customStyle = null;
  76. this.textWrapper = null;
  77.  
  78. this.leftArrow = null;
  79. this.rightArrow = null;
  80.  
  81. this.bottomMenu = null;
  82.  
  83. this.canPreload = false;
  84. this.shouldFitHeight = false;
  85. //for deferring functions that need image dimensions
  86. this.mainImgLoaded = false;
  87.  
  88. this.mouseTimer = null;
  89. this.lastMousePos = {x: 0, y: 0};
  90. //keycode object. Better than remembering what each code does.
  91. this.keys = {38: 'up', 40: 'down', 37: 'left', 39: 'right', 27: 'esc', 86:'v'};
  92.  
  93. this.open = function() {
  94. var V = window._4ChanImageViewer;
  95. // === Start constructing the viewer === //
  96. console.log("Building 4chan Image Viewer");
  97.  
  98. var currentThreadId = document.getElementsByClassName('thread')[0].id;
  99.  
  100. //check if its the last thread opened, if so, remember where the index was.
  101. if(V.getPersistentValue(THREAD_KEY) === currentThreadId){
  102. V.linkIndex = parseInt(V.getPersistentValue(INDEX_KEY));
  103. }else{
  104. V.linkIndex = 0;
  105. V.setPersistentValue(INDEX_KEY,0);
  106. }
  107.  
  108. //set thread id
  109. V.setPersistentValue(THREAD_KEY,currentThreadId);
  110.  
  111. //reset post array
  112. V.postData.length = 0;
  113.  
  114. //add keybinding listener
  115. //Yeah, so, unsafeWindow is used here instead because at least in Tampermonkey
  116. //the safe window can fail to remove event listeners.
  117. unsafeWindow.addEventListener('keydown',V.arrowKeyListener,false);
  118. unsafeWindow.addEventListener('mousemove',V.menuWatcher,false);
  119.  
  120. //grab postContainers
  121. var posts = document.getElementById('delform').getElementsByClassName('postContainer');
  122.  
  123. //get image links and post messages from posts
  124. var plength = posts.length;
  125. for(var i = 0; i < plength; ++i){
  126.  
  127. var file = posts[i].getElementsByClassName('file')[0];
  128. if(file){
  129. var currentLink = file.getElementsByClassName('fileThumb')[0].href;
  130. if(!currentLink){continue;}
  131. var type = V.getElementType(currentLink);
  132. var currentPostBlock = posts[i].getElementsByClassName('postMessage')[0];
  133. var currentPostBacklinks = posts[i].getElementsByClassName('backlink')[0];
  134.  
  135. var blockQuote = document.createElement('blockQuote');
  136. var backlinks = document.createElement('div');
  137.  
  138. if(currentPostBlock){
  139. blockQuote.className = currentPostBlock.className + ' viewerBlockQuote';
  140. blockQuote.innerHTML = currentPostBlock.innerHTML;
  141. V.add4chanListenersToLinks(blockQuote.getElementsByClassName('quotelink'));
  142. }
  143. if(currentPostBacklinks){
  144. backlinks.className = currentPostBacklinks.className + ' viewerBacklinks';
  145. backlinks.innerHTML = currentPostBacklinks.innerHTML;
  146. V.add4chanListenersToLinks(backlinks.getElementsByClassName('quotelink'));
  147. }
  148.  
  149. V.postData.push({'imgSrc':currentLink,'type':type,'mBlock':blockQuote,'backlinks':backlinks});
  150. }
  151. }
  152.  
  153. //build wrapper
  154. V.mainView = document.createElement('div');
  155. V.mainView.id = VIEW_ID;
  156. V.mainView.addEventListener('click',V.confirmExit, false);
  157.  
  158. document.body.appendChild(V.mainView);
  159.  
  160. //set up flex box for centering content
  161. V.centerBox = document.createElement('div');
  162. V.centerBox.id = IMG_BOX_ID;
  163. V.mainView.appendChild(V.centerBox);
  164.  
  165. //build image tag
  166. V.mainImg = document.createElement(V.postData[V.linkIndex].type);
  167. V.mainImg.src = V.postData[V.linkIndex].imgSrc;
  168. V.mainImg.id = IMG_ID;
  169. V.mainImg.classList.add("hideCursor");
  170. V.mainImg.autoplay = true;
  171. V.mainImg.controls = false;
  172. V.mainImg.loop = true;
  173.  
  174. var imgDiv = document.createElement('div');
  175. imgDiv.id = IMG_WRAPPER_ID;
  176. imgDiv.appendChild(V.mainImg);
  177. V.centerBox.appendChild(imgDiv);
  178.  
  179. V.mainImg.addEventListener('click',V.clickImg,false);
  180.  
  181. //set shouldFit Height so image can know about it if it loads before menuInit()
  182. var isHeight = V.getPersistentValue(HEIGHT_KEY);
  183. V.shouldFitHeight = isHeight? true : false;
  184. V.mainImg.onload = function(){
  185. window._4ChanImageViewer.imageLoadHandler();
  186. };
  187.  
  188. //start preloading to next image index
  189. V.canPreload = true;
  190. window.setTimeout(function(){V.runImagePreloading(V.linkIndex);},100);
  191.  
  192.  
  193. //add quote block/backlinks(first image always has second post quote)
  194. V.textWrapper = document.createElement('div');
  195. V.textWrapper.addEventListener('click',V.eventStopper,false);
  196. V.textWrapper.id = 'viewerTextWrapper';
  197. V.textWrapper.appendChild(V.postData[V.linkIndex].backlinks);
  198. V.textWrapper.appendChild(V.postData[V.linkIndex].mBlock);
  199. V.centerBox.appendChild(V.textWrapper);
  200.  
  201.  
  202. //build top layer
  203. V.topLayer = document.createElement('div');
  204. V.topLayer.innerHTML = "&nbsp;";
  205. V.topLayer.id=TOP_LAYER_ID;
  206.  
  207. document.body.appendChild(V.topLayer);
  208.  
  209.  
  210. //build custom style tag
  211. V.customStyle = document.createElement('style');
  212. V.customStyle.innerHTML = STYLE_TEXT;
  213. document.body.appendChild(V.customStyle);
  214.  
  215. //build bottom menu
  216. var formHtml = '<label><input id="'+WIDTH_KEY+'" type="checkbox" checked="checked" />Fit Image to Width</label>\
  217. <span>|</span>\
  218. <label><input id="'+HEIGHT_KEY+'" type="checkbox" />Fit Image to Height</label>\
  219. ';
  220. V.bottomMenu = document.createElement('form');
  221. V.bottomMenu.id = "viewerBottomMenu";
  222. V.bottomMenu.className = 'hidden';
  223. V.bottomMenu.innerHTML = formHtml;
  224. document.body.appendChild(V.bottomMenu);
  225. V.bottomMenu.addEventListener('click',V.menuClickHandler,false);
  226. V.menuInit();
  227.  
  228. //build arrow buttons
  229. V.leftArrow = document.createElement("div");
  230. V.leftArrow.innerHTML = '<span>&#9001;</span>';
  231. V.leftArrow.id = "previousImageButton";
  232. V.leftArrow.classList.add("pagingButtons","hidden");
  233.  
  234. V.rightArrow = document.createElement("div");
  235. V.rightArrow.innerHTML = '<span>&#9002;</span>';
  236. V.rightArrow.id = "nextImageButton";
  237. V.rightArrow.classList.add("pagingButtons","hidden");
  238.  
  239. V.leftArrow.addEventListener('click',function(event){event.stopImmediatePropagation();V.previousImg();},false);
  240. V.rightArrow.addEventListener('click',function(event){event.stopImmediatePropagation();V.nextImg();},false);
  241. V.mainView.appendChild(V.leftArrow);
  242. V.mainView.appendChild(V.rightArrow);
  243.  
  244.  
  245. //some fixes for weird behaviors
  246. V.centerBox.style.outline = '0';
  247. V.centerBox.tabIndex = 1;
  248. V.centerBox.focus();
  249. };
  250. this.menuInit = function(){
  251. var V = window._4ChanImageViewer;
  252. var menuControls = V.bottomMenu.getElementsByTagName('input');
  253. for(var i = 0; i < menuControls.length; ++i){
  254. var input = menuControls[i];
  255. var cookieValue = V.getPersistentValue(input.id);
  256.  
  257. if(cookieValue === 'true'){
  258. input.checked = true;
  259. }else if(cookieValue === 'false'){
  260. input.checked = false;
  261. }
  262. input.parentElement.classList.toggle('flash',input.checked);
  263. switch(input.id){
  264. case WIDTH_KEY:
  265. V.setFitToScreenWidth(input.checked);
  266. break;
  267. case HEIGHT_KEY:
  268. V.setFitToScreenHeight(input.checked);
  269. break;
  270. }
  271. }
  272. };
  273.  
  274. this.menuClickHandler = function(){
  275. var V = window._4ChanImageViewer;
  276. var menuControls = V.bottomMenu.getElementsByTagName('input');
  277.  
  278. for(var i = 0; i < menuControls.length; ++i){
  279. var input = menuControls[i];
  280.  
  281. switch(input.id){
  282. case WIDTH_KEY:
  283. V.setFitToScreenWidth(input.checked);
  284. break;
  285.  
  286. case HEIGHT_KEY:
  287. V.setFitToScreenHeight(input.checked);
  288. break;
  289. }
  290.  
  291. input.parentElement.classList.toggle('flash',input.checked);
  292.  
  293. V.setPersistentValue(input.id,input.checked);
  294.  
  295. }
  296.  
  297. };
  298.  
  299. this.windowClick = function(event){
  300. var V = window._4ChanImageViewer;
  301. event.preventDefault();
  302. event.stopImmediatePropagation();
  303. V.nextImg();
  304.  
  305. };
  306.  
  307. this.add4chanListenersToLinks = function(linkCollection){
  308. for(var i = 0; i < linkCollection.length; ++i){
  309. //These are the functions that 4chan uses
  310. linkCollection[i].addEventListener("mouseover", Main.onThreadMouseOver, false);
  311. linkCollection[i].addEventListener("mouseout", Main.onThreadMouseOut, false);
  312.  
  313. }
  314.  
  315. };
  316.  
  317. /* Event function for determining behavior of viewer keypresses */
  318. this.arrowKeyListener = function(evt){
  319. var V = window._4ChanImageViewer;
  320. switch(V.keys[evt.keyCode]){
  321. case 'right':
  322. V.nextImg();
  323. break;
  324.  
  325. case 'left':
  326. V.previousImg();
  327. break;
  328.  
  329. case 'esc':
  330. V.remove();
  331. break;
  332. }
  333. };
  334.  
  335. /* preloads images starting with the index provided */
  336. this.runImagePreloading = function(index){
  337. var V = window._4ChanImageViewer;
  338.  
  339. if(V && index < V.postData.length){
  340.  
  341. if(V.canPreload){
  342. if(V.postData[index].type === 'VIDEO'){
  343. V.runImagePreloading(index+1);
  344. }else{
  345. var newImage = document.createElement(V.postData[index].type);
  346.  
  347. var loadFunc = function(){V.runImagePreloading(index+1);};
  348. switch(V.postData[index].type){
  349. case 'VIDEO':
  350. newImage.oncanplaythrough = loadFunc;
  351. break;
  352. case 'IMG':
  353. newImage.onload = loadFunc;
  354. break;
  355. }
  356. newImage.onerror = function(){
  357. V.runImagePreloading(index+1);
  358. };
  359.  
  360. newImage.src = V.postData[index].imgSrc;
  361. }
  362.  
  363. }
  364. }
  365. };
  366.  
  367. /* Sets the img and message to the next one in the list*/
  368. this.nextImg = function () {
  369. var V = window._4ChanImageViewer;
  370. if (V.linkIndex === V.postData.length - 1) {
  371. V.topLayer.style.background = 'linear-gradient(to right,rgba(0,0,0,0) 90%,rgba(125,185,232,1) 100%)';
  372. V.topLayer.style.opacity = '.5';
  373. V.topLayer.style.visibility = "visible";
  374.  
  375. setTimeout(function () {
  376. V.topLayer.style.opacity = '0';
  377. setTimeout(function () {
  378. V.topLayer.style.visibility = "hidden";
  379. }, 200);
  380. }, 500);
  381. return;
  382. }
  383. else {
  384. V.changeData(1);
  385. }
  386. };
  387.  
  388. /* Sets the img and message to the previous one in the list*/
  389. this.previousImg = function () {
  390. var V = window._4ChanImageViewer;
  391. if (V.linkIndex === 0) {
  392.  
  393. V.topLayer.style.background = 'linear-gradient(to left,rgba(0,0,0,0) 90%,rgba(125,185,232,1) 100%)';
  394. V.topLayer.style.opacity = '.5';
  395. V.topLayer.style.visibility = "visible";
  396.  
  397. setTimeout(function () {
  398. V.topLayer.style.opacity = '0';
  399. setTimeout(function () {
  400. V.topLayer.style.visibility = "hidden";
  401. }, 200);
  402. }, 500);
  403.  
  404. return;
  405. }
  406. else {
  407. V.changeData(-1);
  408. }
  409. };
  410.  
  411. this.changeData = function(delta){
  412. var V = window._4ChanImageViewer;
  413. V.mainImgLoaded = false;
  414.  
  415. V.linkIndex = V.linkIndex + delta;
  416.  
  417. if(V.postData[V.linkIndex].type !== V.mainImg.tagName){
  418. V.mainImg = V.replaceElement(V.mainImg,V.postData[V.linkIndex].type);
  419. }
  420. console.log('Opening: "' + V.postData[V.linkIndex].imgSrc +'" at index ' + V.linkIndex);
  421. V.mainImg.src = V.postData[V.linkIndex].imgSrc;
  422.  
  423. V.textWrapper.replaceChild(V.postData[V.linkIndex].backlinks,V.postData[V.linkIndex - delta].backlinks);
  424. V.textWrapper.replaceChild(V.postData[V.linkIndex].mBlock,V.postData[V.linkIndex - delta].mBlock);
  425.  
  426. V.mainView.scrollTop = 0;
  427.  
  428. V.setPersistentValue(INDEX_KEY,V.linkIndex);
  429. };
  430.  
  431. this.getElementType = function(src){
  432. if(src.match(/\.(?:(?:webm)|(?:ogg)|(?:mp4))$/)){
  433. return 'VIDEO';
  434. }else{
  435. return 'IMG';
  436. }
  437. };
  438.  
  439. this.replaceElement = function(element,newType){
  440. var V = window._4ChanImageViewer;
  441. var newElement = document.createElement(newType);
  442.  
  443. newElement.className = element.className;
  444. newElement.id = element.id;
  445. newElement.style = element.style;
  446. newElement.autoplay = element.autoplay;
  447. newElement.controls = element.controls;
  448. newElement.loop = element.loop;
  449.  
  450. newElement.addEventListener('click',V.clickImg,false);
  451. newElement.onload = function(){
  452. window._4ChanImageViewer.imageLoadHandler();
  453. };
  454. element.parentElement.insertBefore(newElement,element);
  455. element.parentElement.removeChild(element);
  456. return newElement;
  457. };
  458.  
  459.  
  460.  
  461. /* Function for handling click image events*/
  462. this.clickImg = function(event){
  463. var V = window._4ChanImageViewer;
  464. event.stopPropagation();
  465. V.nextImg();
  466.  
  467. };
  468.  
  469. this.eventStopper = function(event){
  470. if(event.target.nodeName !== 'A'){
  471. event.stopPropagation();
  472. }
  473. };
  474.  
  475. this.confirmExit = function(){
  476. var V = window._4ChanImageViewer;
  477. if(window.confirm('Exit Viewer?')){
  478. V.remove();
  479. }
  480. };
  481.  
  482. /* Removes the view and cleans up handlers*/
  483. this.remove = function(){
  484. var V = window._4ChanImageViewer;
  485. unsafeWindow.removeEventListener('keydown',V.arrowKeyListener,false);
  486. unsafeWindow.removeEventListener('mousemove',V.menuWatcher,false);
  487. document.body.removeEventListener('click',V.windowClick,true);
  488. document.body.removeChild(V.topLayer);
  489. document.body.removeChild(V.mainView);
  490. document.body.removeChild(V.customStyle);
  491. document.body.removeChild(V.bottomMenu);
  492. document.body.style.overflow="auto";
  493. V.canPreload = false;
  494. window.setTimeout(function(){
  495. delete window._4ChanImageViewer;
  496. },10);
  497. };
  498.  
  499.  
  500. /*Mouse-move Handler that watches for when menus should appear and mouse behavior*/
  501. this.menuWatcher = function(event){
  502. var V = window._4ChanImageViewer;
  503. var height_offset = window.innerHeight - V.bottomMenu.offsetHeight;
  504. var width_offset = window.innerWidth - V.bottomMenu.offsetWidth;
  505. var center = window.innerHeight / 2;
  506. var halfArrow = V.leftArrow.offsetHeight / 2;
  507.  
  508. if(event.clientX >= width_offset && event.clientY >= height_offset){
  509. V.bottomMenu.className='bottomMenuShow';
  510. }else if(V.bottomMenu.className==='bottomMenuShow'){
  511. V.bottomMenu.className ='hidden';
  512. }
  513.  
  514. if((event.clientX <= (100) || event.clientX >= (window.innerWidth-100)) &&
  515. (event.clientY <= (center + halfArrow) && event.clientY >= (center - halfArrow))){
  516. V.rightArrow.classList.remove('hidden');
  517. V.leftArrow.classList.remove('hidden');
  518. }else{
  519. V.rightArrow.classList.add('hidden');
  520. V.leftArrow.classList.add('hidden');
  521. }
  522.  
  523. //avoids chrome treating mouseclicks as mousemoves
  524. if(event.clientX !== V.lastMousePos.x && event.clientY !== V.lastMousePos.y){
  525. //mouse click moves to next image when invisible
  526. V.mainImg.classList.remove('hideCursor');
  527.  
  528. window.clearTimeout(V.mouseTimer);
  529. document.body.removeEventListener('click',V.windowClick,true);
  530. document.body.classList.remove('hideCursor');
  531. if(event.target.id === V.mainImg.id){
  532. //hide cursor if it stops, show if it moves
  533. V.mouseTimer = window.setTimeout(function(){
  534. V.mainImg.classList.add('hideCursor');
  535. document.body.classList.add('hideCursor');
  536. document.body.addEventListener('click',V.windowClick,true);
  537. }, 200);
  538. }
  539.  
  540. }
  541.  
  542. V.lastMousePos.x = event.clientX;
  543. V.lastMousePos.y = event.clientY;
  544.  
  545. };
  546.  
  547. /*Stores a key value pair as a cookie*/
  548. this.setPersistentValue = function(key, value){
  549. document.cookie = key + '='+value + ';expires=Thu, 01 Jan 3000 00:00:00 UTC;domain=.4chan.org;path=/';
  550. };
  551.  
  552. /* Retrieves a cookie value via its key*/
  553. this.getPersistentValue = function(key){
  554. var cookieMatch = document.cookie.match(new RegExp(key+'\\s*=\\s*([^;]+)'));
  555. if(cookieMatch){
  556. return cookieMatch[1];
  557. }else{
  558. return null;
  559. }
  560.  
  561.  
  562. };
  563.  
  564. this.setFitToScreenHeight = function(shouldFitImage){
  565. var V = window._4ChanImageViewer;
  566. V.shouldFitHeight = shouldFitImage;
  567. //ignore if image has no height as it is likely not loaded.
  568. if(shouldFitImage && V.mainImg.naturalHeight){
  569. V.fitHeightToScreen();
  570. }else{
  571. V.mainImg.style.maxHeight = '';
  572. }
  573. };
  574. this.setFitToScreenWidth = function(shouldFitImage){
  575. var V = window._4ChanImageViewer;
  576. V.mainImg.style.maxWidth = shouldFitImage ? '100%' : 'none';
  577. };
  578.  
  579.  
  580. this.imageLoadHandler = function(){
  581. var V = window._4ChanImageViewer;
  582. if(V.shouldFitHeight) {
  583. V.fitHeightToScreen();
  584. }
  585. };
  586.  
  587. /* Fits image to screen height*/
  588. this.fitHeightToScreen = function(){
  589. var V = window._4ChanImageViewer;
  590. //sets the changeable properties to the image's real size
  591. var height = V.mainImg.naturalHeight;
  592. V.mainImg.style.maxHeight = height + 'px';
  593.  
  594. //actually tests if it is too high including padding
  595. var heightDiff = (V.mainImg.clientHeight > height)?
  596. V.mainImg.clientHeight - V.mainView.clientHeight:
  597. height - V.mainView.clientHeight;
  598.  
  599. if(heightDiff > 0){
  600. V.mainImg.style.maxHeight = (height - heightDiff) + 'px';
  601. }else{
  602. V.mainImg.style.maxHeight = height + 'px';
  603. }
  604. };
  605.  
  606.  
  607. };//end return function
  608. })();
  609.  
  610.  
  611.  
  612. //Build the open button
  613. var openBttn = document.createElement('button');
  614. openBttn.style.position = 'fixed';
  615. openBttn.style.bottom = '0';
  616. openBttn.style.right = '0';
  617. openBttn.innerHTML = "Open Viewer";
  618. openBttn.addEventListener('click',function(){
  619. //make the viewer and put it on the window so we can clean it up later
  620. window._4ChanImageViewer = new Viewer();
  621. window._4ChanImageViewer.open();
  622. },false);
  623. document.body.appendChild(openBttn);
  624.  
  625.  
  626.  
  627.  
  628.  
  629.  
  630.  
  631.