图片区域绘制

图片区域绘制

需求

  • 在图片上绘制一个不规则闭合图形,形成一个区域,在此区域上做扩展。重点就是生成此区域。

思路

  1. 先根据用户的操作来绘制/修改区域
  2. 保存所有区域及坐标信息
  3. 将坐标信息动态的放在图片上,扩展操作

正文

  • 首先先在html里面把背景图和canvas标签及相应布局写好
    <div id="app">
        canvas画布
        <br />
        <canvas id="myCanvas" width="800" height="500"></canvas>
        <br />
        <button id="btn">生成路径</button>
        带路径的图片
        <br />
        <div id="imgWrap" class="img-wrap" style="width: 800px;height: 500px">
            <img src="./Web前端资料/img/1南宫污水厂_看图王.jpg" style="width: 800px;height: 500px" id="bgImg" />
        </div>
    </div>
  • js里面写相关的逻辑
  1. 监听click事件,判断用户是不是在已画的区域中click的,是的话return,不是的话判断用户是不是当前区域的第一个点,如果是的话,创建新区域,然后创建用户点击的第一个点,不是的话就只创建点,这个点归属于区域列表中的最后一个。最后判断这个点是不是跟这个区域的第一个点位置相同(是否区域闭合了),如果是的话则这个区域就完成了。
  2. 监听mousedown和mousemove事件,判断当前所有区域都闭合了。然后保存mousedown时的位置信息。如果mousedown点击的位置在某个区域内,则所有区域的坐标位置移动mousemove与mousedown的差值,实现拖动区域的效果。如果mousedown点击的位置在某个区域的某个点上,则修改这个点的位置差值,实现拖动某个点的效果
  3. 封装函数,因为拖动的效果,其实是canvas重绘刷新出来的,所以我们将重绘的逻辑写到一个函数里面,然后保证每次坐标更新后立刻清空重绘,就可以做出相应的动画效果了。
    // 创建画布
    const canvas = document.getElementById("myCanvas");
    const ctx = canvas.getContext("2d");
    // 定义状态,分为非连线状态、连线状态
    let lineStatus = false;
    // 创建多个区域数组,用于存放多个区域的数据
    const areas = [];
    // 用于判断用户是要画点连线还是要移动/修改位置
    let canDraw = true;
    // 用户选中了某个区域,该区域在数组中的下标
    let selectAreaIndex = -1;
    // 用户选中了某个区域中的某个路径点,改点在这个区域所有点中的下标
    let selectPointIndex = -1;

    // 添加各个监听事件
    // 添加点击事件,内部判断是画点还是画点和线,同时构造区域和点对象,将area对象保存在数组中
    canvas.onclick = (e) => {
        if (!canDraw) return; // 判断用户选中了已画的某个区域,就不再画点了
        let firstPoint = false; // 是不是这个区域的第一个点
        let area = null;
        if (!lineStatus) { // 第一个点
            lineStatus = true;
            area = new Area();
            firstPoint = true;
        } else if (areas.length > 0) {
            area = areas[areas.length - 1];
        }
        const x = e.pageX - canvas.offsetLeft;
        const y = e.pageY - canvas.offsetTop;
        const index = clickPointIndex(x, y, area); //判断当前点击的位置在不在已画的某个点上
        const p = new Point(x, y);
        if (index === -1) area.add(p); // 判断是继续画点的
        // 先画之前所有的区域,在画本次未完成的点线
        drawAll(ctx, areas.filter((i, j) => (j < areas.length - 1 || firstPoint)));
        drawPoint(ctx, area);
        ctx.beginPath();
        drawLine(ctx, area);

        //判断这次画的点是不是这个区域的第一个点,如果是,这个区域就闭合了
        if (index === 0) {
            lineStatus = false;
            ctx.closePath();
            ctx.fillStyle = 'red';
            ctx.fill();
            ctx.restore();
            // ctx.addHitRegion({id: areas.length + 1});
        } else if(index === -1) { // 新区域首个点,保存数据到数组中
            if (firstPoint) areas.push(area);
        }
    }

    let isMouseDown = false; // 这个要和鼠标移动配合使用
    let downx, downy; //记录首次鼠标点击下来的坐标信息
    canvas.onmousedown = (e) => {
        const x = e.pageX - canvas.offsetLeft;
        const y = e.pageY - canvas.offsetTop;
        downx = x;
        downy = y;
        isMouseDown = true;
        if (!lineStatus) { // 这个状态表示,目前是所有区域都闭合的状态,此时在某个区域长按,可以移动该区域。长按某个区域的路径点,可以伸缩变化改点和区域的位置
            let {pointIndex, areaIndex} = findPointIndex(ctx, areas, x, y); // 先找点,看看用户长按的是不是某个区域的某个点,找到了就不用再找区域了
            if (areaIndex === -1) areaIndex = drawAndFindAreaIndex(ctx, areas, x, y);//找不到点,再找区域,看看用户是不是长按了某个区域
            if (areaIndex >= 0) {
                canDraw = false;
                selectAreaIndex = areaIndex;
                selectPointIndex = pointIndex;
            } else { //没找到,证明用户是想新建区域画点。
                canDraw = true;
            }
        }
    }
    canvas.onmouseup = (e) => { // 重置部分数据
        isMouseDown = false;
        drawAll(ctx, areas)
        selectAreaIndex = -1;
        selectPointIndex = -1;
    }
    canvas.onmousemove = (e) => { // 移动的时候,计算差值,然后改变区域的位置,重新绘制
        if (!isMouseDown || lineStatus || selectAreaIndex === -1) return;
        const x = e.pageX - canvas.offsetLeft;
        const y = e.pageY - canvas.offsetTop;
        const disx = x - downx;
        const disy = y - downy;
        const {points} = areas[selectAreaIndex];
        if (selectPointIndex >= 0) {
            points[selectPointIndex].x += disx;
            points[selectPointIndex].y += disy;
        } else {
            points.forEach(p => {
                p.x += disx;
                p.y += disy;
            })
        }
        downx = x;
        downy = y;
        drawAll(ctx, areas, selectAreaIndex);
    }
  • 封装的函数
    class Point { // 点对象
        constructor(x, y) {
            this.x = x;
            this.y = y;
            this.radius = 10;
            this.color = "blue";
            this.isSelected = false;
        }
    }
    class Area { //区域对象
        constructor() {
            this.points = [];
            this.isCompleted = false;
            this.isSelected = false;
            this.link = '';
        }
        add(point) {
            this.points.push(point);
        }
    }

    function drawImg(ctx, img) { // 画背景图图
        ctx.drawImage(img,0,0,canvas.width, canvas.height);
    }

    function drawPoint(ctx, area) { // 画当前区域所有的点
        const {points} = area;
        ctx.save();
        points.forEach(p => {
            ctx.moveTo(p.x, p.y);
            ctx.globalAlpha = 0.85;
            ctx.beginPath();
            ctx.arc(p.x, p.y, p.radius, 0, Math.PI*2);
            ctx.closePath();
            ctx.fillStyle = p.color;
            ctx.strokeStyle = "black";
            ctx.fill();
        })
        ctx.restore();
    }
    function drawLine(ctx, area) { // 画当前区域的所有的线
        const {points} = area;
        points.forEach((p,i) => {
            if (i === 0) {
                ctx.moveTo(p.x, p.y)
            } else {
                ctx.lineTo(p.x, p.y)
            }
        })
        ctx.lineWidth = 1;
        ctx.strokeStyle = 'red';
        ctx.stroke();
    }
    function clickPointIndex(x, y, area) { // 计算当前坐标是不是在当前区域的路径点上,并返回下标
        const {points} = area;
        for(let i = 0; i< points.length; i++) {
            const p = points[i];
            //使用勾股定理计算这个点与圆心之间的距离
            const distanceFromCenter = Math.sqrt(Math.pow(p.x - x, 2)
                + Math.pow(p.y - y, 2));
            if (distanceFromCenter <= p.radius) {
                //停止搜索
                return i;
            }
        }
        return -1;
    }
    function drawAll(ctx, areas, selectAreaIndex = -1) { // 清空画布,画背景图,画所有区域
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        const img=document.getElementById("bgImg");
        drawImg(ctx, img);
        areas.forEach((a,i) => {
            drawPoint(ctx, a);
            ctx.beginPath();
            drawLine(ctx, a);
            ctx.closePath();
            if (i === selectAreaIndex) {
                ctx.fillStyle = 'blue';
            } else {
                ctx.fillStyle = 'red'
            }
            ctx.fill();
        })
    }
    function drawAndFindAreaIndex(ctx, areas, x, y) { // 画所有区域,并且找到当前坐标在哪个区域上,并返回下标
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        const img=document.getElementById("bgImg");
        drawImg(ctx, img);
        let index = -1;
        areas.forEach((a,i) => {
            drawPoint(ctx, a);
            ctx.beginPath();
            drawLine(ctx, a);
            ctx.closePath();
            const inPath = ctx.isPointInPath(x, y);
            if (inPath) index = i;
            ctx.fillStyle = 'red';
            ctx.fill();
        })
        return index;
    }
    function findPointIndex(ctx, areas, x, y) { // 找到当前坐标在哪个区域的哪个坐标点上,并将两个下标返回
        let areaIndex = -1;
        let pointIndex = -1;
        areas.forEach((a, i) => {
            if (areaIndex >= 0) return;
            const index = clickPointIndex(x, y, a);
            if (index >= 0) {
                areaIndex = i;
                pointIndex = index;
            }
        })
        return {
            areaIndex,
            pointIndex
        }
    }
  • 加入点击事件
    // 开始画背景图
    const img = document.getElementById("bgImg");
    // 按钮监听
    const btn = document.getElementById('btn');
    btn.onclick = () => {
        map.innerHTML = null;
        console.log(areas);
        areas.forEach((a, i) => {
            const area = document.createElement('area');
            area.shape = 'poly';
            area.coords = a.points.map(p => `${p.x},${p.y}`).join(',');// .filter((i,j) => j< a.points.length - 1)
            let polygon = a.points.map(item => {
                return {
                    x: item.x,
                    y: item.y
                }
            })
            var con = new Contour(polygon);
            center = con.centroid();

            console.log('x: ' + center.x + ' y: ' + center.y)

            const odiv = document.createElement('div');
            odiv.style.width = '20px'
            odiv.style.height = '20px'
            odiv.style.background = 'red'
            odiv.style.position = 'absolute'
            odiv.style.top = center.y + 'px'
            odiv.style.left = center.x + 'px'
            document.querySelector('#imgWrap').appendChild(odiv)
        })
    }
  • 找区域的中心点
    class Contour {
        constructor(points) {
            this.pts = points || [];
        }
        area() {
            var area = 0;
            var pts = this.pts;
            var nPts = pts.length;
            var j = nPts - 1;
            var p1;
            var p2;

            for (var i = 0; i < nPts; j = i++) {
                p1 = pts[i];
                p2 = pts[j];
                area += p1.x * p2.y;
                area -= p1.y * p2.x;
            }
            area /= 2;

            return area;
        }

        centroid() {
            var pts = this.pts;
            var nPts = pts.length;
            var x = 0;
            var y = 0;
            var f;
            var j = nPts - 1;
            var p1;
            var p2;

            for (var i = 0; i < nPts; j = i++) {
                p1 = pts[i];
                p2 = pts[j];
                f = p1.x * p2.y - p2.x * p1.y;
                x += (p1.x + p2.x) * f;
                y += (p1.y + p2.y) * f;
            }

            f = this.area() * 6;

            // return new Point1(x / f, y / f);

            return {
                x: x / f,
                y: y / f
            }
        }
    }
  • 最后加点css
    <style>
        .img-wrap {
            position: relative;
        }

        .img-wrap img {
            position: absolute;
            top: 0;
            left: 0;
        }
    </style>

总结

利用canvas制作完成,但是后续还有需要优化的地方,例如利用鼠标右键闭合…

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值