Web简易图片浏览器

林彬
2023-12-01

实现功能:旋转、拖拽、鼠标滚轮放大缩小

样式

        .img-viewer {
            overflow: hidden;
            height: 0;
            padding-bottom: 75%;;
        }

        .iv-btn-area {
            display: flex;
            flex-direction: row;
            justify-content: center;
        }

        .iv-btn-area button {
            width: 3rem;
            height: 3rem;
            font-size: 1.2rem;
            margin: 0.5rem;
        }

        .iv-image-area {
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
            overflow: hidden;
        }

Javascript

function ImageView(selector, setting) {
            const scale_rate = 0.2 //滚动鼠标的缩放速率
            /**
             * 绘制图片浏览器
             */
            const image_view = document.getElementById(selector);

            const iv_btn_area = document.createElement('div')
            const iv_image_area = document.createElement('div')
            iv_btn_area.className = 'iv-btn-area'
            iv_image_area.className = 'iv-image-area'

            const iv_amplify = document.createElement('button')
            const iv_shrink = document.createElement('button')
            const iv_turn_clockwise = document.createElement('button')
            const iv_turn_counterclockwise = document.createElement('button')

            iv_amplify.className = 'iv-amplify'
            iv_shrink.className = 'iv-shrink'
            iv_turn_clockwise.className = 'iv-turn-clockwise'
            iv_turn_counterclockwise.className = 'iv-turn-counterclockwise'

            iv_amplify.innerText = '+'
            iv_shrink.innerText = '-'
            iv_turn_clockwise.innerText = '↻'
            iv_turn_counterclockwise.innerText = '↺'

            iv_btn_area.appendChild(iv_amplify)
            iv_btn_area.appendChild(iv_shrink)
            iv_btn_area.appendChild(iv_turn_clockwise)
            iv_btn_area.appendChild(iv_turn_counterclockwise)

            image_view.appendChild(iv_btn_area)
            image_view.appendChild(iv_image_area)
            /**
             * 绑定按键事件
             */
            iv_turn_counterclockwise.onclick = () => {
                image_pool.getCurrentImage().rotate(-90)
            }

            iv_turn_clockwise.onclick = () => {
                image_pool.getCurrentImage().rotate(90)
            }

            iv_amplify.onclick = () => {
                image_pool.getCurrentImage().scale(0.2)
            }
            iv_shrink.onclick = () => {
                image_pool.getCurrentImage().scale(-0.2)
            }

            /**
             * 滚动放大缩小
             */
            iv_image_area.addEventListener('mousewheel', (evt) => {
                evt.preventDefault()
                if (evt.deltaY < 0) {
                    image_pool.getCurrentImage().scale(scale_rate)
                } else {
                    image_pool.getCurrentImage().scale(-scale_rate)
                    image_pool.getCurrentImage().reset(5)
                }
            })
            /**
             * 创建图片缓存池单例
             */
            const image_pool = (function ImagePool() {
                /**
                 * 根据url加载图片
                 * @param url
                 * @returns {Promise<unknown>}
                 */
                function loadImage(url) {
                    return new Promise((resolve, reject) => {
                        let img = new Image()
                        img.src = url
                        img.style.cursor = "pointer"
                        img.onload = () => {
                            resolve(img)
                        }
                    });
                }

                /**
                 * 用于存放网络图片,同时提供图片变形工具
                 * @param url
                 * @returns {Promise<*>}
                 * @constructor
                 */
                async function ImageContainer(url) {
                    /**
                     * 根据图片初始化
                     */
                    let image = await loadImage(url).then((e) => {
                        return e;
                    })
                    let angle = 0
                    let scale = 1
                    let original_width = image.width
                    let original_height = image.height
                    let wh_proportion = original_width / original_height
                    /**
                     * 图片变形器单例,用于生成css transform
                     */
                    let transformer = (function Transformer() {
                        let rotate = 0;
                        let scale = 1;
                        let currentX = 0;
                        let currentY = 0;
                        let offsetX = 0
                        let offsetY = 0
                        return {
                            getCurrentX: () => {
                                return currentX
                            },
                            getCurrentY: () => {
                                return currentY
                            },
                            setRotate(angle) {
                                rotate = angle
                                return this
                            },
                            setScale(size) {
                                scale = size
                                return this
                            },
                            /**
                             * 临时位移变形方法,用于拖拽图片时显示位置
                             * @param offset_1
                             * @param offset_2
                             * @returns {transformer}
                             */
                            trySetTranslateOffset(offset_1, offset_2) {
                                offset_1 /= scale
                                offset_2 /= scale
                                if (angle === 0) {
                                    offsetX = offset_1
                                    offsetY = offset_2
                                } else if (angle === 90) {
                                    offsetX = offset_2
                                    offsetY = -offset_1
                                } else if (angle === 180) {
                                    offsetX = -offset_1
                                    offsetY = -offset_2
                                } else {
                                    offsetX = -offset_2
                                    offsetY = offset_1
                                }
                                return this
                            },
                            /**
                             * 更具偏移量所相对位移变形
                             * @param offset_1
                             * @param offset_2
                             * @returns {transformer}
                             */
                            setTranslateOffset(offset_1, offset_2) {
                                offset_1 /= scale
                                offset_2 /= scale
                                offsetX = 0
                                offsetY = 0
                                if (angle === 0) {
                                    currentX += offset_1
                                    currentY += offset_2
                                } else if (angle === 90) {
                                    currentX += offset_2
                                    currentY -= offset_1
                                } else if (angle === 180) {
                                    currentX -= offset_1
                                    currentY -= offset_2
                                } else {
                                    currentX -= offset_2
                                    currentY += offset_1
                                }
                                return this
                            },
                            /**
                             * 根据与原点的偏移值进行位移变形
                             * @param offset_1
                             * @param offset_2
                             * @returns {transformer}
                             */
                            setTranslate(offset_1, offset_2) {
                                offsetX = 0
                                offsetY = 0
                                if (angle === 0) {
                                    currentX = offset_1
                                    currentY = offset_2
                                } else if (angle === 90) {
                                    currentX = offset_2
                                    currentY = offset_1
                                } else if (angle === 180) {
                                    currentX = offset_1
                                    currentY = offset_2
                                } else {
                                    currentX = offset_2
                                    currentY = offset_1
                                }
                                return this
                            },
                            /**
                             * 生成css
                             * @returns {string}
                             */
                            toString() {
                                return "rotate(" + angle + "deg) " + "scale(" + scale + "," + scale + ") " + "translate(" + (currentX + offsetX) + "px," + (currentY + offsetY) + "px)"
                            },
                            /**
                             * 初始化变形器
                             * @returns {transformer}
                             */
                            init() {
                                rotate = 0;
                                scale = 1;
                                currentX = 0;
                                currentY = 0;
                                offsetX = 0
                                offsetY = 0
                                return this;
                            }
                        }
                    })()

                    /**
                     * 初始化图片容器
                     */
                    function init() {
                        angle = 0
                        scale = 1
                        wh_proportion = original_width / original_height
                        image.style.transform = transformer.init()
                    }

                    /**
                     * 按照原始图像长宽比例,更具长计算宽
                     * @param height
                     * @returns {number}
                     */
                    function computeWidthOfProportion(height) {
                        if (angle / 90 % 2 === 0) {
                            return height * wh_proportion
                        } else {
                            return height / wh_proportion
                        }
                    }

                    /**
                     * 按照原始图像长宽比例,更具宽计算长
                     * @param width
                     * @returns {number}
                     */
                    function computeHeightOfProportion(width) {
                        if (angle / 90 % 2 === 0) {
                            return width / wh_proportion
                        } else {
                            return width * wh_proportion;
                        }
                    }

                    /**
                     * 设置变形(旋转后)后图片的宽度
                     * @param width
                     */
                    function setWidth(width) {
                        if (angle / 90 % 2 === 0) {
                            image.style.width = width + "px"
                        } else {
                            image.style.height = width + "px"
                        }
                    }

                    /**
                     * 获取变形(旋转)后图片的宽度
                     * @returns {number}
                     */
                    function getWidth() {
                        if (angle / 90 % 2 === 0) {
                            return parseInt(image.style.width)
                        } else {
                            return parseInt(image.style.height)
                        }
                    }

                    /**
                     * 设置变形(旋转后)后图片的高度
                     * @param height
                     */
                    function setHeight(height) {
                        if (angle / 90 % 2 === 0) {
                            image.style.height = height + "px"
                        } else {
                            image.style.width = height + "px"
                        }
                    }

                    /**
                     * 获取变形(旋转)后图片的高度
                     * @returns {number}
                     */
                    function getHeight() {
                        if (angle / 90 % 2 === 0) {
                            return parseInt(image.style.height)
                        } else {
                            return parseInt(image.style.width)
                        }
                    }

                    /**
                     * 调整变形后图片的长度和宽度适配容器尺寸
                     */
                    function adaptParent() {
                        if (getWidth() > getHeight()) {
                            setWidth(iv_image_area.offsetWidth)
                            setHeight(computeHeightOfProportion(getWidth()))
                        } else {
                            setHeight(iv_image_area.offsetHeight)
                            setWidth(computeWidthOfProportion(getHeight()))
                        }
                    }

                    /**
                     * 更具缩放倍率缩放图片
                     * @param magnification
                     */
                    function scaleImage(magnification) {
                        scale = scale + magnification < 1 ? 1 : scale + magnification
                        image.style.transform = transformer.setScale(scale)
                    }

                    /**
                     * 拖拽实现
                     */
                    let ScreenX = 0
                    let ScreenY = 0

                    image.addEventListener("dragstart", (evt) => {
                        ScreenX = evt.screenX
                        ScreenY = evt.screenY
                    })
                    image.addEventListener("drag", (evt) => {
                        image.style.transform = transformer.trySetTranslateOffset(evt.screenX - ScreenX, evt.screenY - ScreenY)
                    })
                    image.addEventListener("dragend", (evt) => {
                        image.style.transform = transformer.setTranslateOffset(evt.screenX - ScreenX, evt.screenY - ScreenY)
                    })

                    return {
                        rotate(degree) {
                            angle = (degree + angle + 360) % 360
                            image.style.transform = transformer.setRotate(angle)
                            adaptParent()
                        },
                        scale: scaleImage
                        , getImage() {
                            init()
                            adaptParent()
                            return image
                        }, reset(speed) {
                            if (scale === 1 && (transformer.getCurrentX() !== 0 || transformer.getCurrentY() !== 0)) {
                                image.style.transform = transformer.setTranslate(Math.trunc(transformer.getCurrentX() / speed), Math.trunc(transformer.getCurrentY() / speed))
                            }
                        }
                    }
                }

                let size = 0;
                let index = 0;
                let pool = []
                let url_dict = {}

                return {
                    /**
                     * 向图片浏览器添加图片
                     * @param url
                     * @returns {Promise<number>}
                     */
                    async addImage(url) {
                        pool.push(await ImageContainer(url))
                        url_dict[url] = size
                        size++
                        return size - 1
                    },
                    /**
                     * 设置当前图片,若图片不在缓存池内则加载图片
                     * @param url
                     * @returns {Promise<void>}
                     */
                    async setCurrentImage(url) {
                        if (url_dict[url] !== undefined) {
                            index = url_dict[url]
                        } else {
                            index = await this.addImage(url)
                        }
                    },
                    /**
                     * 获取当前图片容器
                     * @returns {*}
                     */
                    getCurrentImage() {
                        return pool[index]
                    }
                }
            })()

            /**
             * 容器变换时重绘窗口
             * @type {ResizeObserver}
             */
            let observer = new ResizeObserver((evt) => {
                iv_image_area.innerHTML = ""
                iv_image_area.style.height = image_view.offsetHeight - iv_btn_area.offsetHeight + "px"
                let img = image_pool.getCurrentImage()?.getImage()
                if (img) {
                    iv_image_area.appendChild(img)
                }
            });
            observer.observe(image_view);

            return {
                async setImage(url) {
                    await image_pool.setCurrentImage(url)
                }
            }
        }

用法

<div id="image_view" class="img-viewer"></div>
<script>
    let iv = new ImageView("image_view")
    iv..setImage("https://img.io/example.jpg")
</script>
 类似资料: