handlerTakeScreenshot.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. /* eslint-disable no-await-in-loop */
  2. import { sendMessage } from '@/utils/message';
  3. import { sleep } from '@/utils/helper';
  4. function findScrollableElement(
  5. element = document.documentElement,
  6. maxDepth = 5
  7. ) {
  8. if (maxDepth === 0) return null;
  9. const excludeTags = ['SCRIPT', 'STYLE', 'SVG', 'HEAD'];
  10. const isScrollable = element.scrollHeight > window.innerHeight;
  11. if (isScrollable) return element;
  12. for (let index = 0; index < element.childElementCount; index += 1) {
  13. const currentChild = element.children.item(index);
  14. const isExcluded =
  15. currentChild.tagName.includes('-') ||
  16. excludeTags.includes(currentChild.tagName);
  17. if (!isExcluded) {
  18. const scrollableElement = findScrollableElement(
  19. currentChild,
  20. maxDepth - 1
  21. );
  22. if (scrollableElement) return scrollableElement;
  23. }
  24. }
  25. return null;
  26. }
  27. function injectStyle() {
  28. const style = document.createElement('style');
  29. style.innerText =
  30. 'html::-webkit-scrollbar, body::-webkit-scrollbar, .automa-scrollable-el::-webkit-scrollbar{ width: 0 !important; height: 0 !important } body.is-screenshotting [is-sticky] { position: relative !important; } .hide-fixed [is-fixed] {visibility: hidden !important; opacity: 0 !important;}';
  31. style.id = 'automa-css-scroll';
  32. document.body.appendChild(style);
  33. return style;
  34. }
  35. function canvasToBase64(canvas, { format, quality }) {
  36. return canvas.toDataURL(`image/${format}`, quality / 100);
  37. }
  38. function loadAsyncImg(src) {
  39. return new Promise((resolve) => {
  40. const image = new Image();
  41. image.onload = () => {
  42. resolve(image);
  43. };
  44. image.src = src;
  45. });
  46. }
  47. async function takeScreenshot(tabId, options) {
  48. await sendMessage('set:active-tab', tabId, 'background');
  49. const imageUrl = await sendMessage(
  50. 'get:tab-screenshot',
  51. options,
  52. 'background'
  53. );
  54. return imageUrl;
  55. }
  56. async function captureElement({ selector, tabId, options }) {
  57. const element = document.querySelector(selector);
  58. if (!element) {
  59. const error = new Error('element-not-found');
  60. throw error;
  61. }
  62. element.scrollIntoView({
  63. block: 'center',
  64. inline: 'center',
  65. });
  66. await sleep(500);
  67. const imageUrl = await takeScreenshot(tabId, options);
  68. const image = await loadAsyncImg(imageUrl);
  69. const canvas = document.createElement('canvas');
  70. const context = canvas.getContext('2d');
  71. const { height, width, x, y } = element.getBoundingClientRect();
  72. const diffHeight = image.height / window.innerHeight;
  73. const diffWidth = image.width / window.innerWidth;
  74. const newWidth = width * diffWidth;
  75. const newHeight = height * diffHeight;
  76. canvas.width = newWidth;
  77. canvas.height = newHeight;
  78. context.drawImage(
  79. image,
  80. x * diffHeight,
  81. y * diffWidth,
  82. newWidth,
  83. newHeight,
  84. 0,
  85. 0,
  86. newWidth,
  87. newHeight
  88. );
  89. return canvasToBase64(canvas, options);
  90. }
  91. export default async function ({ tabId, options, data: { type, selector } }) {
  92. if (type === 'element') {
  93. const imageUrl = await captureElement({
  94. tabId,
  95. options,
  96. selector,
  97. });
  98. return imageUrl;
  99. }
  100. document.body.classList.add('is-screenshotting');
  101. const style = injectStyle();
  102. const canvas = document.createElement('canvas');
  103. const context = canvas.getContext('2d');
  104. const maxCanvasSize = BROWSER_TYPE === 'firefox' ? 32767 : 65035;
  105. const scrollElement = document.querySelector('.automa-scrollable-el');
  106. let scrollableElement = scrollElement || findScrollableElement();
  107. if (!scrollableElement) {
  108. const imageUrl = await takeScreenshot(tabId, options);
  109. return imageUrl;
  110. }
  111. scrollableElement.classList?.add('automa-scrollable-el');
  112. const originalYPosition = window.scrollY;
  113. let originalScrollHeight = scrollableElement.scrollHeight;
  114. canvas.height =
  115. scrollableElement.scrollHeight > maxCanvasSize
  116. ? maxCanvasSize
  117. : scrollableElement.scrollHeight;
  118. canvas.width = window.innerWidth;
  119. document.body
  120. .querySelectorAll('*:not([is-sticky], [is-fixed])')
  121. .forEach((el) => {
  122. const { position } = getComputedStyle(el);
  123. if (position === 'sticky') el.setAttribute('is-sticky', '');
  124. else if (position === 'fixed') el.setAttribute('is-fixed', '');
  125. });
  126. let scaleDiff = 1;
  127. let scrollPosition = 0;
  128. let canvasAdjusted = false;
  129. if (scrollableElement.tagName === 'HTML') scrollableElement = window;
  130. while (scrollPosition <= originalScrollHeight) {
  131. const imageUrl = await takeScreenshot(tabId, options);
  132. if (scrollPosition > 0 && !document.body.classList.contains('hide-fixed')) {
  133. document.body.classList.add('hide-fixed');
  134. }
  135. const image = await loadAsyncImg(imageUrl);
  136. const newScrollPos = scrollPosition + window.innerHeight;
  137. if (!canvasAdjusted) {
  138. if (canvas.width !== image.width) {
  139. scaleDiff = image.width / window.innerWidth;
  140. canvas.width *= scaleDiff;
  141. canvas.height *= scaleDiff;
  142. originalScrollHeight *= scaleDiff;
  143. if (canvas.height > maxCanvasSize) canvas.height = maxCanvasSize;
  144. }
  145. canvasAdjusted = true;
  146. }
  147. const newWidth = image.width * scaleDiff;
  148. const newHeight = image.height * scaleDiff;
  149. const sourceYPos =
  150. (scrollPosition + window.innerHeight) * scaleDiff - originalScrollHeight;
  151. context.drawImage(
  152. image,
  153. 0,
  154. sourceYPos > 0 ? sourceYPos : 0,
  155. newWidth,
  156. newHeight,
  157. 0,
  158. scrollPosition * scaleDiff,
  159. newWidth,
  160. newHeight
  161. );
  162. scrollPosition = newScrollPos;
  163. scrollableElement.scrollTo(0, newScrollPos);
  164. await sleep(1000);
  165. }
  166. style.remove();
  167. document.body.classList.remove('hide-fixed');
  168. document.body.classList.remove('is-screenshotting');
  169. scrollableElement.scrollTo(0, originalYPosition);
  170. return canvasToBase64(canvas, options);
  171. }