You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

imagediff.js 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. function getDefaultSvgBoundsIfUndefined(svgXml, src) {
  2. const DefaultSize = 300;
  3. const MaxSize = 99999;
  4. const svg = svgXml.rootElement;
  5. const width = svg.width.baseVal;
  6. const height = svg.height.baseVal;
  7. if (width.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE || height.unitType === SVGLength.SVG_LENGTHTYPE_PERCENTAGE) {
  8. const img = new Image();
  9. img.src = src;
  10. if (img.width > 1 && img.width < MaxSize && img.height > 1 && img.height < MaxSize) {
  11. return {
  12. width: img.width,
  13. height: img.height
  14. };
  15. }
  16. if (svg.hasAttribute('viewBox')) {
  17. const viewBox = svg.viewBox.baseVal;
  18. return {
  19. width: DefaultSize,
  20. height: DefaultSize * viewBox.width / viewBox.height
  21. };
  22. }
  23. return {
  24. width: DefaultSize,
  25. height: DefaultSize
  26. };
  27. }
  28. }
  29. export default function initImageDiff() {
  30. function createContext(image1, image2) {
  31. const size1 = {
  32. width: image1 && image1.width || 0,
  33. height: image1 && image1.height || 0
  34. };
  35. const size2 = {
  36. width: image2 && image2.width || 0,
  37. height: image2 && image2.height || 0
  38. };
  39. const max = {
  40. width: Math.max(size2.width, size1.width),
  41. height: Math.max(size2.height, size1.height)
  42. };
  43. return {
  44. image1: $(image1),
  45. image2: $(image2),
  46. size1,
  47. size2,
  48. max,
  49. ratio: [
  50. Math.floor(max.width - size1.width) / 2,
  51. Math.floor(max.height - size1.height) / 2,
  52. Math.floor(max.width - size2.width) / 2,
  53. Math.floor(max.height - size2.height) / 2
  54. ]
  55. };
  56. }
  57. $('.image-diff').each(function() {
  58. const $container = $(this);
  59. const diffContainerWidth = $container.width() - 300;
  60. const pathAfter = $container.data('path-after');
  61. const pathBefore = $container.data('path-before');
  62. const imageInfos = [{
  63. loaded: false,
  64. path: pathAfter,
  65. $image: $container.find('img.image-after'),
  66. $boundsInfo: $container.find('.bounds-info-after')
  67. }, {
  68. loaded: false,
  69. path: pathBefore,
  70. $image: $container.find('img.image-before'),
  71. $boundsInfo: $container.find('.bounds-info-before')
  72. }];
  73. for (const info of imageInfos) {
  74. if (info.$image.length > 0) {
  75. $.ajax({
  76. url: info.path,
  77. success: (data, _, jqXHR) => {
  78. info.$image.on('load', () => {
  79. info.loaded = true;
  80. setReadyIfLoaded();
  81. });
  82. info.$image.attr('src', info.path);
  83. if (jqXHR.getResponseHeader('Content-Type') === 'image/svg+xml') {
  84. const bounds = getDefaultSvgBoundsIfUndefined(data, info.path);
  85. if (bounds) {
  86. info.$image.attr('width', bounds.width);
  87. info.$image.attr('height', bounds.height);
  88. info.$boundsInfo.hide();
  89. }
  90. }
  91. }
  92. });
  93. } else {
  94. info.loaded = true;
  95. setReadyIfLoaded();
  96. }
  97. }
  98. function setReadyIfLoaded() {
  99. if (imageInfos[0].loaded && imageInfos[1].loaded) {
  100. initViews(imageInfos[0].$image, imageInfos[1].$image);
  101. }
  102. }
  103. function initViews($imageAfter, $imageBefore) {
  104. initSideBySide(createContext($imageAfter[0], $imageBefore[0]));
  105. if ($imageAfter.length > 0 && $imageBefore.length > 0) {
  106. initSwipe(createContext($imageAfter[1], $imageBefore[1]));
  107. initOverlay(createContext($imageAfter[2], $imageBefore[2]));
  108. }
  109. $container.find('> .loader').hide();
  110. $container.find('> .hide').removeClass('hide');
  111. }
  112. function initSideBySide(sizes) {
  113. let factor = 1;
  114. if (sizes.max.width > (diffContainerWidth - 24) / 2) {
  115. factor = (diffContainerWidth - 24) / 2 / sizes.max.width;
  116. }
  117. const widthChanged = sizes.image1.length !== 0 && sizes.image2.length !== 0 && sizes.image1[0].naturalWidth !== sizes.image2[0].naturalWidth;
  118. const heightChanged = sizes.image1.length !== 0 && sizes.image2.length !== 0 && sizes.image1[0].naturalHeight !== sizes.image2[0].naturalHeight;
  119. if (sizes.image1.length !== 0) {
  120. $container.find('.bounds-info-after .bounds-info-width').text(`${sizes.image1[0].naturalWidth}px`).addClass(widthChanged ? 'green' : '');
  121. $container.find('.bounds-info-after .bounds-info-height').text(`${sizes.image1[0].naturalHeight}px`).addClass(heightChanged ? 'green' : '');
  122. }
  123. if (sizes.image2.length !== 0) {
  124. $container.find('.bounds-info-before .bounds-info-width').text(`${sizes.image2[0].naturalWidth}px`).addClass(widthChanged ? 'red' : '');
  125. $container.find('.bounds-info-before .bounds-info-height').text(`${sizes.image2[0].naturalHeight}px`).addClass(heightChanged ? 'red' : '');
  126. }
  127. sizes.image1.css({
  128. width: sizes.size1.width * factor,
  129. height: sizes.size1.height * factor
  130. });
  131. sizes.image1.parent().css({
  132. margin: `${sizes.ratio[1] * factor + 15}px ${sizes.ratio[0] * factor}px ${sizes.ratio[1] * factor}px`,
  133. width: sizes.size1.width * factor + 2,
  134. height: sizes.size1.height * factor + 2
  135. });
  136. sizes.image2.css({
  137. width: sizes.size2.width * factor,
  138. height: sizes.size2.height * factor
  139. });
  140. sizes.image2.parent().css({
  141. margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`,
  142. width: sizes.size2.width * factor + 2,
  143. height: sizes.size2.height * factor + 2
  144. });
  145. }
  146. function initSwipe(sizes) {
  147. let factor = 1;
  148. if (sizes.max.width > diffContainerWidth - 12) {
  149. factor = (diffContainerWidth - 12) / sizes.max.width;
  150. }
  151. sizes.image1.css({
  152. width: sizes.size1.width * factor,
  153. height: sizes.size1.height * factor
  154. });
  155. sizes.image1.parent().css({
  156. margin: `0px ${sizes.ratio[0] * factor}px`,
  157. width: sizes.size1.width * factor + 2,
  158. height: sizes.size1.height * factor + 2
  159. });
  160. sizes.image1.parent().parent().css({
  161. padding: `${sizes.ratio[1] * factor}px 0 0 0`,
  162. width: sizes.max.width * factor + 2
  163. });
  164. sizes.image2.css({
  165. width: sizes.size2.width * factor,
  166. height: sizes.size2.height * factor
  167. });
  168. sizes.image2.parent().css({
  169. margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`,
  170. width: sizes.size2.width * factor + 2,
  171. height: sizes.size2.height * factor + 2
  172. });
  173. sizes.image2.parent().parent().css({
  174. width: sizes.max.width * factor + 2,
  175. height: sizes.max.height * factor + 2
  176. });
  177. $container.find('.diff-swipe').css({
  178. width: sizes.max.width * factor + 2,
  179. height: sizes.max.height * factor + 4
  180. });
  181. $container.find('.swipe-bar').on('mousedown', function(e) {
  182. e.preventDefault();
  183. const $swipeBar = $(this);
  184. const $swipeFrame = $swipeBar.parent();
  185. const width = $swipeFrame.width() - $swipeBar.width() - 2;
  186. $(document).on('mousemove.diff-swipe', (e2) => {
  187. e2.preventDefault();
  188. const value = Math.max(0, Math.min(e2.clientX - $swipeFrame.offset().left, width));
  189. $swipeBar.css({
  190. left: value
  191. });
  192. $container.find('.swipe-container').css({
  193. width: $swipeFrame.width() - value
  194. });
  195. $(document).on('mouseup.diff-swipe', () => {
  196. $(document).off('.diff-swipe');
  197. });
  198. });
  199. });
  200. }
  201. function initOverlay(sizes) {
  202. let factor = 1;
  203. if (sizes.max.width > diffContainerWidth - 12) {
  204. factor = (diffContainerWidth - 12) / sizes.max.width;
  205. }
  206. sizes.image1.css({
  207. width: sizes.size1.width * factor,
  208. height: sizes.size1.height * factor
  209. });
  210. sizes.image2.css({
  211. width: sizes.size2.width * factor,
  212. height: sizes.size2.height * factor
  213. });
  214. sizes.image1.parent().css({
  215. margin: `${sizes.ratio[1] * factor}px ${sizes.ratio[0] * factor}px`,
  216. width: sizes.size1.width * factor + 2,
  217. height: sizes.size1.height * factor + 2
  218. });
  219. sizes.image2.parent().css({
  220. margin: `${sizes.ratio[3] * factor}px ${sizes.ratio[2] * factor}px`,
  221. width: sizes.size2.width * factor + 2,
  222. height: sizes.size2.height * factor + 2
  223. });
  224. sizes.image2.parent().parent().css({
  225. width: sizes.max.width * factor + 2,
  226. height: sizes.max.height * factor + 2
  227. });
  228. $container.find('.onion-skin').css({
  229. width: sizes.max.width * factor + 2,
  230. height: sizes.max.height * factor + 4
  231. });
  232. const $range = $container.find("input[type='range'");
  233. const onInput = () => sizes.image1.parent().css({
  234. opacity: $range.val() / 100
  235. });
  236. $range.on('input', onInput);
  237. onInput();
  238. }
  239. });
  240. }