ZoomableImageComponent.razor 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. @using System.Text;
  2. <div id="mainImageContainer" style="display: block;width:@($"{ImageWidthInPx}px");height:@($"{ImageHeightInPx}px");overflow: hidden;">
  3. <div id="imageMover"
  4. @onmousewheel="MouseWheelZooming"
  5. style="@MoveImageStyle">
  6. <div id="imageContainer"
  7. @onmousemove="MouseMoving"
  8. style="@ZoomImageStyle">
  9. @*this div is used just for moving around when zoomed*@
  10. </div>
  11. </div>
  12. </div>
  13. @if (ShowResetButton)
  14. {
  15. <div style="display:block">
  16. <button @onclick="ResetImgage">Reset</button>
  17. </div>
  18. }
  19. @code {
  20. /// <summary>
  21. /// The path or url of the image
  22. /// </summary>
  23. [Parameter]
  24. public string ImageUrlPath { get; set; }
  25. /// <summary>
  26. /// The width of the image
  27. /// </summary>
  28. [Parameter]
  29. public int ImageWidthInPx { get; set; }
  30. /// <summary>
  31. /// The height of the image
  32. /// </summary>
  33. [Parameter]
  34. public int ImageHeightInPx { get; set; }
  35. /// <summary>
  36. /// Set to true to show the reset button
  37. /// </summary>
  38. [Parameter]
  39. public bool ShowResetButton { get; set; }
  40. /// <summary>
  41. /// Set the amount the image is scaled by, default is 0.1f
  42. /// </summary>
  43. [Parameter]
  44. public double DefaultScaleBy { get; set; } = 0.1f;
  45. /// <summary>
  46. /// The Maximum the image can scale to, default = 5f
  47. /// </summary>
  48. [Parameter]
  49. public double ScaleToMaximum { get; set; } = 5f;
  50. /// <summary>
  51. /// Set the speed at which the image is moved by, default 2.
  52. /// 2 or 3 seems to work best.
  53. /// </summary>
  54. [Parameter]
  55. public double DefaultMoveBy { get; set; } = 2;
  56. //defaults
  57. double _CurrentScale = 1.0f;
  58. double _PositionLeft = 0;
  59. double _PositionTop = 0;
  60. double _OldClientX = 0;
  61. double _OldClientY = 0;
  62. double _DefaultMinPosition = 0;//to the top and left
  63. double _DefaultMaxPosition = 0;//to the right and down
  64. //the default settings used to display the image in the child div
  65. private Dictionary<string, string> _ImageContainerStyles;
  66. Dictionary<string, string> ImageContainerStyles
  67. {
  68. get
  69. {
  70. if (_ImageContainerStyles == null)
  71. {
  72. _ImageContainerStyles = new Dictionary<string, string>();
  73. _ImageContainerStyles.Add("width", "100%");
  74. _ImageContainerStyles.Add("height", "100%");
  75. _ImageContainerStyles.Add("position", "relative");
  76. _ImageContainerStyles.Add("background-size", "contain");
  77. _ImageContainerStyles.Add("background-repeat", "no-repeat");
  78. _ImageContainerStyles.Add("background-position", "50% 50%");
  79. _ImageContainerStyles.Add("background-image", $"URL({ImageUrlPath})");
  80. }
  81. return _ImageContainerStyles;
  82. }
  83. }
  84. private Dictionary<string, string> _MovingContainerStyles;
  85. Dictionary<string, string> MovingContainerStyles
  86. {
  87. get
  88. {
  89. if (_MovingContainerStyles == null)
  90. {
  91. InvokeAsync(ResetImgage);
  92. }
  93. return _MovingContainerStyles;
  94. }
  95. }
  96. protected async Task ResetImgage()
  97. {
  98. _PositionLeft = 0;
  99. _PositionTop = 0;
  100. _DefaultMinPosition = 0;
  101. _DefaultMaxPosition = 0;
  102. _CurrentScale = 1.0f;
  103. _MovingContainerStyles = new Dictionary<string, string>();
  104. _MovingContainerStyles.Add("width", "100%");
  105. _MovingContainerStyles.Add("height", "100%");
  106. _MovingContainerStyles.Add("position", "relative");
  107. _MovingContainerStyles.Add("left", $"{_PositionLeft}%");
  108. _MovingContainerStyles.TryAdd("top", $"{_PositionTop}%");
  109. await InvokeAsync(StateHasChanged);
  110. }
  111. string ZoomImageStyle { get => DictionaryToCss(ImageContainerStyles); }
  112. string MoveImageStyle { get => DictionaryToCss(MovingContainerStyles); }
  113. private string DictionaryToCss(Dictionary<string, string> styleDictionary)
  114. {
  115. StringBuilder sb = new StringBuilder();
  116. foreach (var kvp in styleDictionary.AsEnumerable())
  117. {
  118. sb.AppendFormat("{0}:{1};", kvp.Key, kvp.Value);
  119. }
  120. return sb.ToString();
  121. }
  122. protected async void MouseMoving(MouseEventArgs e)
  123. {
  124. //if the mouse button 1 is not down exit the function
  125. if (e.Buttons != 1)
  126. {
  127. _OldClientX = e.ClientX;
  128. _OldClientY = e.ClientY;
  129. return;
  130. }
  131. //get the % of the current scale to move by at least the default move speed plus any scaled changes
  132. //basically the bigger the image the faster it moves..
  133. double scaleFrac = (_CurrentScale / ScaleToMaximum);
  134. double scaleMove = (DefaultMoveBy * (DefaultMoveBy * scaleFrac));
  135. //moving mouse right
  136. if (_OldClientX < e.ClientX)
  137. {
  138. if ((_PositionLeft - DefaultMoveBy) <= _DefaultMaxPosition)
  139. {
  140. _PositionLeft += scaleMove;
  141. }
  142. }
  143. //moving mouse left
  144. if (_OldClientX > e.ClientX)
  145. {
  146. //if (_DefaultMinPosition < (_PositionLeft - DefaultMoveBy))
  147. if ((_PositionLeft + DefaultMoveBy) >= _DefaultMinPosition)
  148. {
  149. _PositionLeft -= scaleMove;
  150. }
  151. }
  152. //moving mouse down
  153. if (_OldClientY < e.ClientY)
  154. {
  155. //if ((_PositionTop + DefaultMoveBy) <= _DefaultMaxPosition)
  156. if ((_PositionTop - DefaultMoveBy) <= _DefaultMaxPosition)
  157. {
  158. _PositionTop += scaleMove;
  159. }
  160. }
  161. //moving mouse up
  162. if (_OldClientY > e.ClientY)
  163. {
  164. //if ((_PositionTop - DefaultMoveBy) > _DefaultMinPosition)
  165. if ((_PositionTop + DefaultMoveBy) >= _DefaultMinPosition)
  166. {
  167. _PositionTop -= scaleMove;
  168. }
  169. }
  170. _OldClientX = e.ClientX;
  171. _OldClientY = e.ClientY;
  172. await UpdateScaleAndPosition();
  173. }
  174. async Task<double> IncreaseScale()
  175. {
  176. return await Task.Run(() =>
  177. {
  178. //increase the scale first then calculate the max and min positions
  179. _CurrentScale += DefaultScaleBy;
  180. double scaleFrac = (_CurrentScale / ScaleToMaximum);
  181. double scaleDiff = (DefaultMoveBy + (DefaultMoveBy * scaleFrac));
  182. double scaleChange = DefaultMoveBy + scaleDiff;
  183. _DefaultMaxPosition += scaleChange;
  184. _DefaultMinPosition -= scaleChange;
  185. return _CurrentScale;
  186. });
  187. }
  188. async Task<double> DecreaseScale()
  189. {
  190. return await Task.Run(() =>
  191. {
  192. _CurrentScale -= DefaultScaleBy;
  193. double scaleFrac = (_CurrentScale / ScaleToMaximum);
  194. double scaleDiff = (DefaultMoveBy + (DefaultMoveBy * scaleFrac));
  195. double scaleChange = DefaultMoveBy + scaleDiff;
  196. _DefaultMaxPosition -= scaleChange;
  197. _DefaultMinPosition += scaleChange;//DefaultMoveBy;
  198. //fix descaling, move the image back into view when descaling (zoomin out)
  199. if (_CurrentScale <= 1)
  200. {
  201. _PositionLeft = 0;
  202. _PositionTop = 0;
  203. }
  204. else
  205. {
  206. //left can not be more than max position
  207. _PositionLeft = (_DefaultMaxPosition < _PositionLeft) ? _DefaultMaxPosition : _PositionLeft;
  208. //top can not be more than max position
  209. _PositionTop = (_DefaultMaxPosition < _PositionTop) ? _DefaultMaxPosition : _PositionTop;
  210. //left can not be less than min position
  211. _PositionLeft = (_DefaultMinPosition > _PositionLeft) ? _DefaultMinPosition : _PositionLeft;
  212. //top can not be less than min position
  213. _PositionTop = (_DefaultMinPosition > _PositionTop) ? _DefaultMinPosition : _PositionTop;
  214. }
  215. return _CurrentScale;
  216. });
  217. }
  218. protected async void MouseWheelZooming(WheelEventArgs e)
  219. {
  220. //holding shift stops the page from scrolling
  221. if (e.ShiftKey == true)
  222. {
  223. if (e.DeltaY > 0)
  224. {
  225. _CurrentScale = ((_CurrentScale + DefaultScaleBy) >= 5) ? _CurrentScale = 5f : await IncreaseScale();
  226. }
  227. if (e.DeltaY < 0)
  228. {
  229. _CurrentScale = ((_CurrentScale - DefaultScaleBy) <= 0) ? _CurrentScale = DefaultScaleBy : await DecreaseScale();
  230. }
  231. await UpdateScaleAndPosition();
  232. }
  233. }
  234. /// <summary>
  235. /// Refresh the values in the moving style dictionary that is used to position the image.
  236. /// </summary>
  237. async Task UpdateScaleAndPosition()
  238. {
  239. await Task.Run(() =>
  240. {
  241. if (!MovingContainerStyles.TryAdd("transform", $"scale({_CurrentScale})"))
  242. {
  243. MovingContainerStyles["transform"] = $"scale({_CurrentScale})";
  244. }
  245. if (!MovingContainerStyles.TryAdd("left", $"{_PositionLeft}%"))
  246. {
  247. MovingContainerStyles["left"] = $"{_PositionLeft}%";
  248. }
  249. if (!MovingContainerStyles.TryAdd("top", $"{_PositionTop}%"))
  250. {
  251. MovingContainerStyles["top"] = $"{_PositionTop}%";
  252. }
  253. });
  254. }
  255. }