实现功能:旋转、拖拽、鼠标滚轮放大缩小
样式
.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 = urlimg.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 = 0let scale = 1let original_width = image.widthlet original_height = image.heightlet 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 = 0let offsetY = 0return {getCurrentX: () => {return currentX},getCurrentY: () => {return currentY},setRotate(angle) {rotate = anglereturn this},setScale(size) {scale = sizereturn this},/*** 临时位移变形方法,用于拖拽图片时显示位置* @param offset_1* @param offset_2* @returns {transformer}*/trySetTranslateOffset(offset_1, offset_2) {offset_1 /= scaleoffset_2 /= scaleif (angle === 0) {offsetX = offset_1offsetY = offset_2} else if (angle === 90) {offsetX = offset_2offsetY = -offset_1} else if (angle === 180) {offsetX = -offset_1offsetY = -offset_2} else {offsetX = -offset_2offsetY = offset_1}return this},/*** 更具偏移量所相对位移变形* @param offset_1* @param offset_2* @returns {transformer}*/setTranslateOffset(offset_1, offset_2) {offset_1 /= scaleoffset_2 /= scaleoffsetX = 0offsetY = 0if (angle === 0) {currentX += offset_1currentY += offset_2} else if (angle === 90) {currentX += offset_2currentY -= offset_1} else if (angle === 180) {currentX -= offset_1currentY -= offset_2} else {currentX -= offset_2currentY += offset_1}return this},/*** 根据与原点的偏移值进行位移变形* @param offset_1* @param offset_2* @returns {transformer}*/setTranslate(offset_1, offset_2) {offsetX = 0offsetY = 0if (angle === 0) {currentX = offset_1currentY = offset_2} else if (angle === 90) {currentX = offset_2currentY = offset_1} else if (angle === 180) {currentX = offset_1currentY = offset_2} else {currentX = offset_2currentY = 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 = 0offsetY = 0return this;}}})()/*** 初始化图片容器*/function init() {angle = 0scale = 1wh_proportion = original_width / original_heightimage.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 + magnificationimage.style.transform = transformer.setScale(scale)}/*** 拖拽实现*/let ScreenX = 0let ScreenY = 0image.addEventListener("dragstart", (evt) => {ScreenX = evt.screenXScreenY = 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) % 360image.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] = sizesize++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>