概念:啥是热区组件
是指在一张图片上选取一些区域,每个区域链接到指定的地址。可以在图片上设置多个热区区域并配置相应的数据。
在正式开始之前,先了解几个概念
HTML <area> 元素 在图片上定义一个热点区域,可以关联一个超链接。元素仅在
了解更多:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/area
HTML <map> 属性 与 <area> 属性一起使用来定义一个图像映射(一个可点击的链接区域)。
了解更多:https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/map
基本的热区实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<img src="https://cdn.pixabay.com/photo/2021/07/16/18/28/czech-republic-6471576__480.jpg" usemap="#planetmap"
alt="Planets" />
<map name="planetmap" id="planetmap">
<area shape="rect" coords="0,0,110,260" href="https://baidu.com" target="_blank" alt="Venus" />
<area shape="rect" coords="110,260,210,360" href="https://taobao.com" target="_blank" alt="Mercury" />
<area shape="rect" coords="210,360,410,410" href="https://qq.com" target="_blank" alt="Sun" />
</map>
</body>
</html>
上面的区域是我们提前在代码中编写好的,但我们更加希望可以动态的框选区域。
注:以下使用 React 框架作为演示
React 实现框选区域成为热区
分析:其实我们只需要知道 x, y, width, height ,就可以绘制热区。
import React from 'react'
import MultiCrops from 'react-multi-crops'
import img from './test.webp'
class App extends React.Component {
state = {
coordinates: [],
x: 0,
y: 0,
width: 0,
height: 0,
str: '',
imgShow: false
}
changeCoordinate = (coordinate, index, coordinates) => {
this.setState({
coordinates,
})
setTimeout(() => {
let {x, y, width, height} = this.state.coordinates[0]
this.setState({
str: `${x},${y},${width},${height}`
})
this.setState({
imgShow: true
})
}, 1000)
}
deleteCoordinate = (coordinate, index, coordinates) => {
this.setState({
coordinates,
})
}
render() {
const { str, imgShow } = this.state
console.log(str)
return (
<div>
{
imgShow ? <img src={img} useMap="#planetmap" alt="Planets" /> : <MultiCrops
src={img}
coordinates={this.state.coordinates}
onChange={this.changeCoordinate}
onDelete={this.deleteCoordinate}
/>
}
<map name="planetmap" id="planetmap">
<area shape="rect" coords={str} href="https://baidu.com" target="_blank" alt="Venus" />
</map>
</div>
)
}
}
export default App;
本着 no zuo no die 的精神,可以继续研究一下,裁剪框是怎么形成的。这里主要用到了 canvas 技术。
绘制裁剪框
import React, { useState, useEffect } from "react";
const ImgCrop = ({ file }) => {
const { url } = file;
const [originImg, setOriginImg] = useState();
const [contentNode, setContentNode] = useState();
const [canvasNode, setCanvasNode] = useState();
const [startCoordinate, setStartCoordinate] = useState([0, 0]);
const [dragging, setDragging] = useState(false);
const [curPoisition, setCurPoisition] = useState(null);
const [hotAreaCoordStr, setHotAreaCootdStr] = useState("");
const [imgShow, setImgShow] = useState(false);
const initCanvas = () => {
if (url == null) {
return;
}
if (contentNode == null) {
return;
}
if (canvasNode == null) {
return;
}
const image = new Image();
setOriginImg(image);
image.addEventListener("load", () => {
const ctx = canvasNode.getContext("2d");
ctx.clearRect(0, 0, canvasNode.width, canvasNode.height);
const clientW = contentNode.clientWidth;
const size = image.width / clientW;
if (image.width > clientW) {
canvasNode.width = clientW;
canvasNode.height = image.height / size;
} else {
canvasNode.width = image.width;
canvasNode.height = image.height;
}
ctx.drawImage(image, 0, 0, canvasNode.width, canvasNode.height);
});
image.crossOrigin = "anonymous";
image.src = url;
};
useEffect(() => {
initCanvas();
}, [canvasNode, url]);
const handleMouseDownEvent = (e) => {
setDragging(true);
const { offsetX, offsetY } = e.nativeEvent;
setStartCoordinate([offsetX, offsetY]);
};
const handleMouseMoveEvent = (e) => {
if (!dragging) {
return;
}
const ctx = canvasNode.getContext("2d");
ctx.clearRect(0, 0, canvasNode.width, canvasNode.height);
const { offsetX, offsetY } = e.nativeEvent;
const tempWidth = offsetX - startCoordinate[0];
const tempHeight = offsetY - startCoordinate[1];
drawTrim(startCoordinate[0], startCoordinate[1], tempWidth, tempHeight);
};
const handleMouseRemoveEvent = () => {
setDragging(false);
if (curPoisition == null) {
return;
}
const { startX, startY, width, height } = curPoisition;
setHotAreaCootdStr(`${startX},${startY},${width},${height}`);
setImgShow(true);
};
const drawTrim = (x, y, w, h) => {
const ctx = canvasNode.getContext("2d");
ctx.save();
ctx.fillStyle = "rgba(0,0,0,0.6)";
ctx.fillRect(0, 0, canvasNode.width, canvasNode.height);
ctx.globalCompositeOperation = "source-atop";
ctx.clearRect(x, y, w, h);
ctx.globalCompositeOperation = "source-over";
drawBorderPixel(ctx, x, y, w, h);
setCurPoisition({
width: w,
height: h,
startX: x,
startY: y,
position: [
(x, y),
(x + w, y),
(x, y + h),
(x + w, y + h),
(x + w / 2, y),
(x + w / 2, y + h),
(x, y + h / 2),
(x + w, y + h / 2),
],
canvasWidth: canvasNode.width,
});
ctx.restore();
ctx.save();
ctx.globalCompositeOperation = "destination-over";
ctx.drawImage(originImg, 0, 0, canvasNode.width, canvasNode.height);
ctx.restore();
};
const drawBorderPixel = (ctx, x, y, w, h) => {
ctx.fillStyle = "#f5222d";
const size = 5;
ctx.fillRect(x - size / 2, y - size / 2, size, size);
ctx.fillRect(x + w - size / 2, y - size / 2, size, size);
ctx.fillRect(x - size / 2, y + h - size / 2, size, size);
ctx.fillRect(x + w - size / 2, y + h - size / 2, size, size);
ctx.fillRect(x + w / 2 - size / 2, y - size / 2, size, size);
ctx.fillRect(x + w / 2 - size / 2, y + h - size / 2, size, size);
ctx.fillRect(x - size / 2, y + h / 2 - size / 2, size, size);
ctx.fillRect(x + w - size / 2, y + h / 2 - size / 2, size, size);
};
return (
<>
{imgShow ? (
<img src={url} useMap="#planetmap" width={`${curPoisition.canvasWidth}`} alt="Planets" />
) : (
<section ref={setContentNode}>
<canvas
ref={setCanvasNode}
onMouseDown={handleMouseDownEvent}
onMouseMove={handleMouseMoveEvent}
onMouseUp={handleMouseRemoveEvent}
/>
</section>
)}
<map name="planetmap" id="planetmap">
<area
shape="rect"
coords={hotAreaCoordStr}
href="https://baidu.com"
target="_blank"
alt="Venus"
/>
</map>
</>
);
};
function App() {
let obj = {
file: {
url: "https://cdn.pixabay.com/photo/2021/07/18/14/59/family-6475821__480.jpg",
},
};
return <ImgCrop {...obj} />;
}
export default App;
参考文章:https://segmentfault.com/a/1190000022285488
|