From da65bdc5260c4942f72383184f43e5c2cd03426e Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 21:33:02 +0000 Subject: [PATCH] feat: Add loading spinner during image initialization Adds a visual loading spinner to the `visual-image-tool`. The spinner is displayed when the tool is instantiated and the image is loading. It is hidden once the image is fully loaded and the tool's UI elements are ready. This improves your experience by providing visual feedback during the initial loading phase, preventing the perception of unresponsiveness. Changes include: - Added `this.spinnerElement` and `this.initialLoadComplete` fields. - Created and styled a spinner DIV element in `_prepareContainer()`. - Added CSS keyframes for the spinner animation. - Implemented logic in `_updateScaling()` to hide the spinner upon initial image load. - Ensured the spinner element is removed in the `destroy()` method. --- src/visual-image-tool.js | 56 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/visual-image-tool.js b/src/visual-image-tool.js index d994fe9..9b8a4cb 100644 --- a/src/visual-image-tool.js +++ b/src/visual-image-tool.js @@ -97,6 +97,9 @@ class VisualImageTool { startMouseY: 0, }; + this.spinnerElement = null; + this.initialLoadComplete = false; + // Initialize the tool this._init(); } @@ -133,6 +136,37 @@ class VisualImageTool { // Ensure the parent is positioned if (this.imageElement.parentNode) { this.imageElement.parentNode.style.position = "relative"; + + // Create spinner element + this.spinnerElement = document.createElement("div"); + this.spinnerElement.style.position = "absolute"; + this.spinnerElement.style.top = "50%"; + this.spinnerElement.style.left = "50%"; + this.spinnerElement.style.transform = "translate(-50%, -50%)"; + this.spinnerElement.style.width = "40px"; + this.spinnerElement.style.height = "40px"; + this.spinnerElement.style.border = "4px solid #f3f3f3"; // Light grey + this.spinnerElement.style.borderTop = "4px solid #3498db"; // Blue + this.spinnerElement.style.borderRadius = "50%"; + this.spinnerElement.style.boxSizing = "border-box"; + this.spinnerElement.style.zIndex = "1000"; // Ensure it's above other elements + this.spinnerElement.style.display = "block"; // Initially visible + this.spinnerElement.style.animation = "spin 1s linear infinite"; + + this.imageElement.parentNode.appendChild(this.spinnerElement); + + // Add CSS keyframes for spinner animation + if (!document.getElementById("visual-image-tool-spinner-styles")) { + const styleElement = document.createElement("style"); + styleElement.id = "visual-image-tool-spinner-styles"; + styleElement.innerHTML = ` +@keyframes spin { + 0% { transform: translate(-50%, -50%) rotate(0deg); } + 100% { transform: translate(-50%, -50%) rotate(360deg); } +} + `; + document.head.appendChild(styleElement); + } } } @@ -143,11 +177,28 @@ class VisualImageTool { _updateScaling() { this.state.originalWidth = this.imageElement.naturalWidth || 1; this.state.originalHeight = this.imageElement.naturalHeight || 1; + + // Hide spinner once image is loaded and dimensions are available + if ( + !this.initialLoadComplete && + this.spinnerElement && + this.state.originalWidth > 1 && + this.state.originalHeight > 1 + ) { + this.spinnerElement.style.display = "none"; + this.initialLoadComplete = true; + } this.state.displayWidth = this.imageElement.offsetWidth; this.state.displayHeight = this.imageElement.offsetHeight; this.state.scaleX = this.state.displayWidth / this.state.originalWidth; this.state.scaleY = this.state.displayHeight / this.state.originalHeight; + // Update spinner position during initial load if still visible + if (!this.initialLoadComplete && this.spinnerElement) { + this.spinnerElement.style.top = `${this.state.displayHeight / 2}px`; + this.spinnerElement.style.left = `${this.state.displayWidth / 2}px`; + } + // Reposition elements if active if (this.state.focusActive) { this._updateFocusMarkerPosition(); @@ -915,6 +966,11 @@ class VisualImageTool { this.state.cropOverlay.parentNode.removeChild(this.state.cropOverlay); } + if (this.spinnerElement?.parentNode) { + this.spinnerElement.parentNode.removeChild(this.spinnerElement); + } + this.spinnerElement = null; + // Remove event listeners window.removeEventListener("resize", this._updateScaling.bind(this)); document.removeEventListener("mouseup", this._handleMouseUp.bind(this));