@zoom-image/core
Version:
A core implementation of zoom image
1 lines • 10.9 kB
Source Map (JSON)
{"version":3,"sources":["../src/createZoomImageHover.ts"],"names":[],"mappings":"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","sourcesContent":["import { createStore } from \"@namnode/store\"\nimport { imageLoader } from \"./imageLoader\"\nimport { ZoomedImgStatus } from \"./types\"\nimport { clamp, disableScroll, enableScroll, getSourceImage } from \"./utils\"\n\nexport type ZoomImageHoverOptions = {\n customZoom: { width: number; height: number }\n zoomImageSource?: string\n zoomLensClass?: string\n zoomLensScale?: number\n zoomTarget: HTMLElement\n zoomTargetClass?: string\n scale?: number\n disableScrollLock?: boolean\n zoomImageProps?: {\n alt?: string\n }\n}\n\nexport type ZoomImageHoverState = {\n zoomedImgStatus: ZoomedImgStatus\n enabled: boolean\n}\n\nexport type ZoomImageHoverStateUpdate = { enabled: boolean }\n\nexport function createZoomImageHover(container: HTMLElement, options: ZoomImageHoverOptions) {\n const controller = new AbortController()\n const { signal } = controller\n const sourceImgElement = getSourceImage(container)\n const zoomedImgWrapper = document.createElement(\"div\")\n zoomedImgWrapper.style.overflow = \"hidden\"\n const zoomedImg = zoomedImgWrapper.appendChild(document.createElement(\"img\"))\n zoomedImg.alt = options.zoomImageProps?.alt || \"\"\n zoomedImg.style.maxWidth = \"none\"\n zoomedImg.style.display = \"none\"\n const zoomLens = container.appendChild(document.createElement(\"div\"))\n zoomLens.style.display = \"none\"\n\n // Sometimes, source image element's width and height are not available until the image is loaded\n // and container size is set. These values are expected to be same as the container's width and height\n let sourceImageElementWidth = 0\n let sourceImageElementHeight = 0\n\n const finalOptions: Omit<Required<ZoomImageHoverOptions>, \"zoomImageProps\"> = {\n zoomImageSource: options.zoomImageSource || sourceImgElement.src,\n zoomLensClass: options.zoomLensClass || \"\",\n zoomTargetClass: options.zoomTargetClass || \"\",\n customZoom: options.customZoom,\n scale: options.scale || 2,\n zoomTarget: options.zoomTarget,\n zoomLensScale: options.zoomLensScale || 1,\n disableScrollLock: options.disableScrollLock || false,\n }\n\n const {\n scale,\n zoomImageSource,\n customZoom,\n zoomLensClass,\n zoomTarget,\n zoomLensScale,\n zoomTargetClass,\n disableScrollLock,\n } = finalOptions\n\n const store = createStore<ZoomImageHoverState>({\n zoomedImgStatus: \"idle\",\n enabled: true,\n })\n\n let offset: { left: number; top: number } = getOffset(sourceImgElement)\n\n function getOffset(element: HTMLElement) {\n const elRect = element.getBoundingClientRect()\n return { left: elRect.left, top: elRect.top }\n }\n\n function getLimitX(value: number) {\n return sourceImageElementWidth - value\n }\n\n function getLimitY(value: number) {\n return sourceImageElementHeight - value\n }\n\n function zoomLensLeft(left: number) {\n const minX = zoomLens.clientWidth / 2\n return clamp(left, minX, getLimitX(minX)) - minX\n }\n\n function zoomLensTop(top: number) {\n const minY = zoomLens.clientHeight / 2\n return clamp(top, minY, getLimitY(minY)) - minY\n }\n\n function processZoom(event: PointerEvent) {\n let offsetX: number\n let offsetY: number\n let backgroundX: number\n let backgroundY: number\n if (offset) {\n offsetX = zoomLensLeft(event.clientX - offset.left)\n offsetY = zoomLensTop(event.clientY - offset.top)\n backgroundX = (offsetX * scale) / zoomLensScale\n backgroundY = (offsetY * scale) / zoomLensScale\n zoomedImg.style.transform = \"translate(\" + -backgroundX + \"px,\" + -backgroundY + \"px)\"\n zoomLens.style.cssText += \"transform:\" + \"translate(\" + offsetX + \"px,\" + offsetY + \"px);\"\n }\n }\n\n async function handlePointerEnter() {\n imageLoader.createZoomImage(zoomedImg, zoomImageSource, store)\n zoomedImg.style.display = \"block\"\n zoomLens.style.display = \"block\"\n\n if (zoomTargetClass) {\n const classes = zoomTargetClass.split(\" \")\n classes.forEach((className) => zoomTarget.classList.add(className))\n }\n\n if (!disableScrollLock) disableScroll()\n }\n\n function handlePointerLeave() {\n zoomedImg.style.display = \"none\"\n zoomLens.style.display = \"none\"\n\n if (zoomTargetClass) {\n const classes = zoomTargetClass.split(\" \")\n classes.forEach((className) => zoomTarget.classList.remove(className))\n }\n\n if (!disableScrollLock) enableScroll()\n }\n\n function handleScroll() {\n offset = getOffset(sourceImgElement)\n }\n\n async function setup() {\n if (zoomLensClass) {\n zoomLens.className = zoomLensClass\n } else {\n zoomLens.style.background = \"rgba(238, 130, 238, 0.5)\"\n }\n\n // setup event listeners\n container.addEventListener(\"pointerdown\", processZoom, { signal })\n container.addEventListener(\"pointermove\", processZoom, { signal })\n container.addEventListener(\"pointerenter\", handlePointerEnter, { signal })\n container.addEventListener(\"pointerleave\", handlePointerLeave, { signal })\n window.addEventListener(\"scroll\", handleScroll, { signal })\n container.addEventListener(\"touchend\", enableScroll, { signal })\n\n // Append zoomed image wrapper to zoom target\n zoomTarget.appendChild(zoomedImgWrapper)\n\n // Wait for next tick to get container size\n await new Promise((resolve) => setTimeout(resolve, 1))\n const containerRect = container.getBoundingClientRect()\n sourceImageElementWidth = containerRect.width\n sourceImageElementHeight = containerRect.height\n\n // Set up styles if custom zoom available\n if (customZoom) {\n zoomedImgWrapper.style.width = customZoom.width + \"px\"\n zoomedImgWrapper.style.height = customZoom.height + \"px\"\n } else {\n // Else default zoom to source image size\n zoomedImgWrapper.style.width = sourceImageElementWidth + \"px\"\n zoomedImgWrapper.style.height = sourceImageElementHeight + \"px\"\n }\n\n zoomedImg.width = (sourceImageElementWidth * scale) / zoomLensScale\n zoomedImg.height = (sourceImageElementHeight * scale) / zoomLensScale\n\n // Setup default zoom lens style\n const sourceImageRect = sourceImgElement.getBoundingClientRect()\n const fromLeft = sourceImageRect.left - containerRect.left\n const fromTop = sourceImageRect.top - containerRect.top\n zoomTarget.style.pointerEvents = \"none\"\n zoomLens.style.position = \"absolute\"\n zoomLens.style.left = fromLeft + \"px\"\n zoomLens.style.top = fromTop + \"px\"\n zoomLens.style.width = (customZoom.width / scale) * zoomLensScale + \"px\"\n zoomLens.style.height = (customZoom.height / scale) * zoomLensScale + \"px\"\n }\n\n setup()\n\n return {\n cleanup: () => {\n controller.abort()\n container.contains(zoomLens) && container.removeChild(zoomLens)\n\n if (zoomTarget && zoomTarget.contains(zoomedImgWrapper)) {\n zoomTarget.removeChild(zoomedImgWrapper)\n return\n }\n\n container.contains(zoomedImgWrapper) && container.removeChild(zoomedImgWrapper)\n },\n subscribe: store.subscribe,\n getState: store.getState,\n setState: (newState: ZoomImageHoverStateUpdate) => {\n store.setState(newState)\n },\n }\n}\n"]}