实现功能:旋转、拖拽、鼠标滚轮放大缩小
样式
.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>