一、UI
二、实现
<div class="carBrightSpot">
<!-- 轮播图 -->
<div class="block-swiper">
<div class="wrapper">
<swiper :options="swiperOption" id="swiper">
<swiper-slide v-for="(item,index) of carImageResult" :key="item.id">
<!-- <img class="swiper-img" :src="item.image"> -->
<canvas width="800" height="600" :ref="index" @click="toLineBottom(index)"></canvas>
</swiper-slide>
</swiper>
</div>
<!-- 分页器 -->
<div class="myPagination">
<div class="swiper-pagination" slot="pagination"></div>
</div>
</div>
<!-- 热区的亮点 -->
<div class="brightSpot">
<div class="oneBrightSpot" v-if="curCarHotspotPositionResultList.length==0">请点击</div>
<div class="oneBrightSpot" v-else @click="goDetail(item.id)" v-for="item in curCarHotspotPositionResultList">{{item.postName}}</div>
</div>
</div>
// 车亮点
carImageResult:[{
carHotspotResultList:[]
}],
// 当前点击圆点的亮点数组
curCarHotspotPositionResultList:[],
pageNumber:1,
pageSize:100,
// 背景图片缓存
imgs: [new Image(),new Image(),new Image()],
// canvas对象数组
contexts: [],
// 保存位置数组
allPositionArr:[],
// 保存未点击状态的canvas
canvasHistory:[]
// 轮播图选项
swiperOption: {
pagination: ".swiper-pagination", //分页器
paginationClickable: true, //可点击原点切换
autoplayDisableOnInteraction: false, //用户操作swiper之后,是否禁止autoplay。默认为true:停止。如果设置为false,用户操作swiper之后自动切换不会停止,每次都会重新启动autoplay。
// loop: true,//可循环(到达最后一张继续切换从第一张开始滑动)
// autoplay: 1500,//可选选项,自动切换的时间间隔(单位ms),不设定该参数slide不会自动切换。
//用于切换时,去掉其他轮播图的垂直线(若存在)
onSlideChangeEnd:(swiper)=>{
//用于切换右上角显示的该底图的全部热点区域
let swiperActiveIndex = swiper.activeIndex;
this.activeIndex = swiperActiveIndex;
let vm = this;
// 切换时回归状态
// 清空当前点击圆点亮点数组
vm.curCarHotspotPositionResultList = [];
// console.log("ss",this.activeIndex);
// alert(swiper.activeIndex);
// 重绘无线画面
let canvasWidth = vm.getChangeFit(690,'w');
let canvasHeight = vm.getChangeFit(630,'h');
let canvasHistory = vm.canvasHistory;
canvasHistory.map((item,index)=>{
if(index==swiperActiveIndex){
return;
}
let img = new Image();
// 获取保存的无线画布状态
img.src = item;
img.onload = function(){
// 清除画布
vm.contexts[index].clearRect(0, 0, canvasWidth, canvasHeight);
// 重绘画布
vm.contexts[index].drawImage(img,0,0);
}
})
}
},
// 获取数据
getCatImageList(){
const vm =this;
let params={
pageNumber:vm.pageNumber,
pageSize:vm.pageSize,
moduleId:vm.moduleId,
carId:vm.carId,
}
this.api(vm, '/api/app/car/getCatImageList.json', params, function (res) {
let data = res;
vm.carImageResult = data;
vm.$nextTick(()=>{
data.map((item,index)=>{
vm.imgs[index].src = item.image;
// vm.img.src = res[1].image;
const canvas = vm.$refs[index][0];
// 设置画布大小
// UI图轮播图宽度
let canvasWidth = vm.getChangeFit(690,'w');
// 这个距离为UI图轮播图上边框到下面文本框上边框的距离(为了画垂直线到文本框上面)
let canvasHeight = vm.getChangeFit(630,'h');
// 动态设置canvas画布的宽高
canvas.setAttribute('width',canvasWidth+'px');
canvas.setAttribute('height',canvasHeight+'px');
// 获取2D上下文
vm.contexts.push(canvas.getContext('2d'));
// 获取图片大小(UI轮播图大小)
let width = vm.getChangeFit(690,'w');
let height = vm.getChangeFit(516,'h');
// 绘制图片
vm.imgs[index].onload = function(){
let orginWidth = vm.imgs[index].width;
let orginHeight = vm.imgs[index].height;
// console.log("sdfsd",orginWidth,orginHeight);
vm.contexts[index].drawImage(vm.imgs[index],0,0,width,height);
// // 绘制圆点
vm.drawCircle(index,width,height,orginWidth,orginHeight);
}
})
})
},function(err){
// vm.fallBack = true;
})
},
// 适配单位
getChangeFit(uiNumber,type){
let clientWidth = document.documentElement.clientWidth;
let clientHeight = document.documentElement.clientHeight;
let changeNumber = '';
if(type=='w'){
changeNumber = Math.floor(clientWidth*uiNumber/750);
}else{
changeNumber = Math.floor(clientHeight*uiNumber/1200);
}
return changeNumber;
},
// 适配圆点坐标
getImgFit(ox,oy,originImgWidth,originImgHeight,curImgWidth,curImgHeight){
let x = Math.floor(curImgWidth*ox/originImgWidth);
let y = Math.floor(curImgHeight*oy/originImgHeight);
return {x:x,y:y};
},
// 画一张底图全部圆
drawCircle(index,curImgWidth,curImgHeight,originImgWidth,originImgHeight){
const vm = this;
// 该底图中的全部圆点的坐标
let positionArr = vm.carImageResult[index].carHotspotResultList;
// 绘制圆点
positionArr.map(item=>{
//开始绘制新路径(画圆)
// 计算坐标换算
let point = vm.getImgFit(item.x,item.y,originImgWidth,originImgHeight,curImgWidth,curImgHeight);
let x = point.x;
let y = point.y;
// 将换算后的点放入保存所有坐标点的数据
// console.log("vm.allPositionArr.length",vm.allPositionArr.length,index);
if(vm.allPositionArr.length<index+1){
// 第一次创建数组
vm.allPositionArr[index] = [];
vm.allPositionArr[index].push({x,y});
}else{
// 第一次之后
vm.allPositionArr[index].push({x,y});
}
// 绘制圆
vm.contexts[index].beginPath();
vm.contexts[index].arc(x, y, 7, 0, 2*Math.PI, false);
vm.contexts[index].closePath(); //关闭路径
vm.contexts[index].strokeStyle = "rgb(20, 193, 253)"; // 描边颜色为蓝色
vm.contexts[index].lineWidth = 2; //指定描边线的宽度
//以填充方式绘制圆
vm.contexts[index].stroke();
})
const canvas = vm.$refs[index][0];
// 保存无线状态的canvas画布上的图形
vm.canvasHistory.push(canvas.toDataURL())
},
// 画线
drawLine(sx,sy,ex,ey,index,pindex){
const vm = this;
vm.curCarHotspotPositionResultList = vm.carImageResult[index].carHotspotResultList[pindex].carHotspotPositionResultList;
// 重绘无线画面
let canvasWidth = vm.getChangeFit(690,'w');
let canvasHeight = vm.getChangeFit(630,'h');
let img = new Image();
// 获取保存的无线画布状态
img.src = vm.canvasHistory[index];
img.onload = function(){
// 清除画布
vm.contexts[index].clearRect(0, 0, canvasWidth, canvasHeight);
// 重绘画布
vm.contexts[index].drawImage(img,0,0);
// 绘制直线路径
vm.contexts[index].beginPath();
// console.log("contexts",vm.contexts[index]);
vm.contexts[index].moveTo(sx, sy); //画线的起点
vm.contexts[index].lineTo(ex, ey); //终点
vm.contexts[index].closePath();
vm.contexts[index].strokeStyle = "rgb(20, 193, 253)";
vm.contexts[index].stroke();
}
},
// 点击圆点画线
toLineBottom(cindex){
// 点击的底图当前所有圆点坐标
let curPosition = this.allPositionArr[cindex];
// 获取鼠标点击的坐标
let position = this.getEventPosition(event,cindex);
// 计算鼠标点击的坐标和每个圆点的距离来判断是否点击到该圆点
curPosition.map((item,index)=>{
// 两点距离小于半径则是点击该圆点
if(this.calculateDistance(item.x,item.y,position.x,position.y)){
// 画垂直线
// 这里1000足够大即可,超出部分画布不会显示的,y+6是得再圆点坐标下面即圆边开始画线
this.drawLine(item.x,item.y+6,item.x,1000,cindex,index)
}
})
},
// 获取鼠标点击位置
getEventPosition(ev,index){
// 轮播图所需(一张图的宽度)
let canvasWidth = this.getChangeFit(690,'w');
var x, y;
if (ev.layerX || ev.layerX == 0) {
// 轮播图第二、三。。张图距离左边的距离需要加上前面相应张数轮播图的宽度大小之和
x = ev.layerX +canvasWidth*index;
y = ev.layerY;
} else if (ev.offsetX || ev.offsetX == 0) { // Opera
x = ev.offsetX+canvasWidth*index;
y = ev.offsetY;
}
return {x: x, y: y};
},
// 计算点和点距离(计算是否点击到圆)
calculateDistance(sx,sy,ex,ey){
var dis = Math.sqrt((sx - ex) * (sx - ex) + (sy - ey) * (sy - ey));//Math.sqrt()求平方跟
if(dis <= 7){
return true;
}else{
return false
}
}
三、总结
步骤
(1)首先在每个轮播图里放一个canvas画布。用于每个画布上绘制背景图和圆圈和垂直线。
(2)在每个画布上绘制轮播图,轮播图的大小因为移动端要适配宽高,所有需要使用一个函数去计算,实际宽高=UI中宽高比例*实际屏幕宽高。
(3)画布的大小也需要计算,宽度与轮播图宽度一致,高度需要从轮播图上边框的距离到提示框上边框的的距离进行比例计算。(用于后面限制绘制的垂直线的长度)
(4)绘制圆圈,需要坐标X,Y,这里后台给的是相对于原图的X,Y故需要获取原图的宽高,再进行比例计算。当前宽高=轮播图宽高*x/y坐标相对于原图宽高的比例。
(5)同时因为有3个轮播图,每个轮播图中都有一个画布,故需要用一个数组去存储创建的canvas的2D上下文用于绘制。
(6)点击圆点并画垂直线。
1)获取鼠标点击时相对于点击容器的X,Y坐标。
2)将当前底图中所有点和鼠标点击的坐标进行计算,计算两点的距离,若两点距离小于半径,则视为点击了该圆圈。
3)以该点击的圆的X,Y坐标为基准,Y坐标增加一些后以该为起点绘制一条垂直线,终点只要保证X与起点一致,Y可无限大,因为画布就那么大超出不会显示。
来源:https://blog.csdn.net/qq_40542728/article/details/98751913