快应用原生组件搭建室内地图

最近开发快应用的时候,我现快应用没有自带的相关室内地图构建功能,有的只是调用百度地图的室外地图功能。
(不过这可能是个共有的盲点,其他前端也都得用原生组件搭建室内地图吧)
进入正题,先给大家看搭建的效果:
在这里插入图片描述
其中“+”“-”实现缩放,“B”实现复位功能,“-1”处是选择楼层

下面我将一步一步地讲解如何构建室内地图。

1.图片缩放、平移、复位、切换

这些是地图的基本构建元素,我们先从这个问题做起。
快应用可能由于生态还不健全,这一块还找不到直接的学习资料。不过快应用是基于H5,JS的,咱们可以从这些地方找。
这里参考了这篇博客:使用HTML5 canvas做地图(3)图片加载平移放大缩小
但从H5转化到快应用还是有很多工作要做的,这里当做大家有canvas的经验了,直接贴代码了


<template>
  <div class="main">
    <div class="footer-container">
      
      <input class="button" type="button" value="+" onclick="largeScale"></input>
      <input class="button" type="button" value="-" onclick="smallScale"></input>
      <input class="button" type="button" value="B" onclick="middleScale"></input>

      <select class="button" onchange="pickMap" >
        <option for="{{floor}}"   selected={{$item.selected}}>{{$item.id}}</option>
      </select>
    </div>
    <div class="content">
       <canvas id="indoormap" ontouchstart ='onMouseDown' ontouchmove='onMouseMove'></canvas>
    </div>
    
  </div>
</template>

<script>

export default{
  data:{
    x1:'', //平移用的坐标
    x2:'',
    y1:'',
    y2:'',
    context2D:'',
    img:'',
    imgX:0,
    imgY:0,
    imgScale:1,
    floor:[
      {'id':-1,'selected':'true',pic:'../Common/1.jpg'},//粘贴你自己的图片地址
      {'id':2,'selected':'false',pic:'../Common/a.jpg'},
      {'id':3,'selected':'false',pic:'../Common/3.jpg'}
    ],
  },
  pickMap(e){
    for(var i=0;i<this.floor.length;i++){
      if(this.floor[i].id==e.newValue){
        this.img.src=this.floor[i].pic
      }
    }
    this.img.width=1000
    this.img.height=1500
    this.clearImage()
    this.img.onload = () => {
      this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
    }   
  },
  onShow() {  
    const canvas = this.$element('indoormap') //获取 canvas 组件
    this.context2D = canvas.getContext('2d') //获取 canvas 绘图上下文
    this.img = new Image()
    for(var i=0;i<this.floor.length;i++){
      if(this.floor[i].selected=='true'){
        this.img.src=this.floor[i].pic
      }
    }
    this.img.onload = () => {
      this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
    }   
  },
  
  onMouseDown(event){
    const data=event.touches[0]
    this.x1=data.clientX
    this.y1=data.clientY
    //
  },
  onMouseMove(event){
    //擦除原来的图片
    this.clearImage()

    const data=event.touches[0]
    this.x2=data.clientX
    this.y2=data.clientY
    this.imgX+=this.x2-this.x1
    this.imgY+=this.y2-this.y1
    console.log(this.imgY)
    //console.log(event.touches[0])
    this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)

    this.x1=this.x2
    this.y1=this.y2
    
  },
  largeScale(){
    this.clearImage()
    if(this.imgScale<2.4){this.imgScale+=0.2}
    this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
  },
  smallScale(){
    this.clearImage()
   
    if(this.imgScale>0.6)this.imgScale-=0.2

    this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
  },
  middleScale(){
    this.clearImage()
   
    this.imgScale=1
    this.imgX=0
    this.imgY=0
    this.drawImage(this.img,this.imgX,this.imgY,this.imgScale)
  },
  drawImage(img,imgX,imgY,imgScale){
    this.context2D.drawImage(img,0,0,img.width,img.height,
      imgX,imgY,img.width*imgScale,img.height*imgScale)
  },
  clearImage(){
    this.context2D.clearRect(this.imgX,this.imgY,this.img.width*this.imgScale,this.img.height*this.imgScale)
  }
}
</script>

<style>
  .main{
    flex-direction:column;
    flex-wrap:wrap;  
    align-content:flex-start; 
  }
  .content{
    width:100%;
    height:1500px;
  }
  .footer-container {	
      position: fixed;
      flex-direction:column;	
      width:100px;
      height: 600px;	
      top:400px;	
      left:0;	
      background-color: transparent;
  }
  .button{
    text-align: center;
    font-size: 40px;
    font-family: '微软雅黑';
    margin-top:40px;
    width:100px;
    height:100px;
    color:white;
    background: linear-gradient(rgb(50, 200, 150),rgb(0, 150, 120));
  }

</style>

2.地图元素构建

从上面一步,大家应该发现一个问题:图片放大就会失真(模糊)!这是因为我们采用的是位图,而地图不应该因放大而模糊,所以我们应该用矢量图。

普通图片在PS下设置路径转成ai文件到Adobe Illustrator,进行颜色填充,可以转化为矢量图。但这个过程不仅复杂,最关键的是快应用不支持打开这种矢量图。

那么,我们只能自己用canvas画出矢量图了,canvas画出的矩阵等图形都是矢量化的。
这里我又参考了一篇博客:基于HTML5 Canvas绘制的支持手势缩放的室内地图
该文章的思路都是基于H5和JS的,后面关于缩放的内容都是不能照搬的。所以才有了第一步工作。

2.1地图数据构建

其中构建地图数据可以通过以下JSON格式,变量名非常明了,这里就不赘述了。

[
{
  "title": "Toilet",
  "x": 0,
  "y": 0,
  "width": 171,
  "height": 283,
  "color": "rgba(76, 181, 216, 0.2)",
  "textcolor": "black",
  "bordercolor": "rgba(76, 181, 216, 1)",
  "imageurl": "http://192.168.1.106:8000/static/images/Toilet.png"
}
,
{
  "title": "UNIQLO",
  "x": 0,
  "y": 284,
  "width": 171,
  "height": 286,
  "color": "rgba(230, 110, 250, 0.2)",
  "textcolor": "black",
  "bordercolor": "rgba(230, 110, 250, 1)",
  "imageurl": "http://192.168.1.106:8000/static/images/UNIQLO.png"
}
]

这样的数据就可以构造出一层楼的地图。多个这样的文件就是一栋建筑的地图数据。

最终实现

最后贴出完整的实现代码


<template>
<div class="main">
    <div class="footer-container">
      
      <input class="button" type="button" value="+" onclick="largeScale"></input>
      <input class="button" type="button" value="-" onclick="smallScale"></input>
      <input class="button" type="button" value="B" onclick="middleScale"></input>

      <select class="button" onchange="pickMap" >
        <option for="{{floor}}"   selected={{$item.selected}}>{{$item.id}}</option>
      </select>
    </div>
    <div class="content" >
       <canvas id="indoormap" ontouchstart ='onMouseDown' ontouchmove='onMouseMove'></canvas>
    </div>
    
  </div>
</template>

<script>
import fetch from '@system.fetch'

export default{
  data:{
    MAPINFO:[],
    floor:[
      {'owner':'building1','id':-1,'selected':'true','mapUrl':'http://192.168.1.106:8000/static/building1/map1.txt'},//这里mapUrl请替换为你的地图数据访问地址
      {'owner':'building1','id':2,'selected':'false','mapUrl':'http://192.168.1.106:8000/static/building1/map2.txt'},
      {'owner':'building1','id':3,'selected':'false','mapUrl':'http://192.168.1.106:8000/static/building1/map3.txt'}
    ],
    x1:'', //平移用的坐标
    x2:'',
    y1:'',
    y2:'',
    devX:0,
    devY:0,
    mapUrl:'',//地图信息url
    canvas:'',
    context2D:'',
    imgScale:1,
  },
  onInit() {  
    for(var i=0;i<this.floor.length;i++){
      if(this.floor[i].selected=='true'){
        this.mapUrl=this.floor[i].mapUrl
      }
    }
    fetch.fetch({
      url:this.mapUrl,
      success: res=>{
        this.MAPINFO=JSON.parse(res.data)
        
        this.canvas = this.$element('indoormap') //获取 canvas 组件
        this.context2D = this.canvas.getContext('2d') //获取 canvas 
        
        this.DrawMapInfo()
      }
    })
    
  },
  pickMap(e){
    for(var i=0;i<this.floor.length;i++){
      if(this.floor[i].id==e.newValue){
        this.mapUrl=this.floor[i].mapUrl
      }
    }
    fetch.fetch({
      url:this.mapUrl,
      success: res=>{
        this.MAPINFO=JSON.parse(res.data)     
        this.DrawMapInfo()
      }
    })
    this.DrawMapInfo() 
  },
  onMouseDown(event){
    const data=event.touches[0]
    this.x1=data.clientX
    this.y1=data.clientY
  },
  onMouseMove(event){
    //擦除原来的图片
    this.clearImage()

    const data=event.touches[0]
    this.x2=data.clientX
    this.y2=data.clientY
    this.devX+=this.x2-this.x1
    this.devY+=this.y2-this.y1
    //console.log(event.touches[0])
    this.DrawMapInfo()

    this.x1=this.x2
    this.y1=this.y2
    
  },
  clearImage(){
    this.context2D.clearRect(0,0,1500,1500)
  },
  largeScale(){
    
    if(this.imgScale<1.6){this.imgScale+=0.2}
    this.DrawMapInfo()
  },
  smallScale(){

    if(this.imgScale>0.6)this.imgScale-=0.2

    this.DrawMapInfo()
  },
  middleScale(){
    this.imgScale=1
    this.devX=0
    this.devY=0
    this.DrawMapInfo()
  },
  DrawMapInfo:function(){
    this.clearImage()
    for (var i = 0; i < this.MAPINFO.length; i++) {
      this.DrawBlock(this.context2D,this.MAPINFO[i].x*this.imgScale+this.devX, 
        this.MAPINFO[i].y*this.imgScale+this.devY, 
        this.MAPINFO[i].width*this.imgScale, this.MAPINFO[i].height*this.imgScale, 
        this.MAPINFO[i].title,  this.MAPINFO[i].color,  this.MAPINFO[i].textcolor, 
        this.MAPINFO[i].bordercolor, this.MAPINFO[i].imageurl)
    }
  },
  DrawBlock:function(context2D,x, y, width, height, text, backgroudcolor, textcolor, bordercolor, imageurl) {

    //画框图
    context2D.fillStyle = backgroudcolor
    context2D.fillRect(x, y, width, height)
    
    //画边界
    context2D.strokeStyle = bordercolor
    context2D.lineWidth = 0.8
    context2D.strokeRect(x, y, width, height)
    //画文字和图标,文字小则不画图
    context2D.fillStyle = textcolor
    const textsize = width * height / 2000
    //console.log(textsize)
    if (textsize > 15) {
      context2D.font = 20 + 'pt Microsoft YaHei'
      if (imageurl != "") {  //画图标
        const image = new Image()
        image.src = imageurl
        //image.onload = () => {
        context2D.drawImage(image, x + width / 7, y + height / 2 - 25 , 30, 30)
        //}
      }
    } 
    else {
      context2D.font = 15 + 'pt Microsoft YaHei'
    }
    context2D.fillText(text, x + width / 7 + 40, y + height / 2)
  },
}
</script>

<style>
  .main{
    background-color: rgba(220, 241, 220, 0.856);
    flex-direction:column;
    flex-wrap:wrap;  
    align-content:flex-start; 
  }
  .indoormap{
    width:100px;
    height:100px;
  }
  .content{
    width:100%;
    height:1500px;
  }
  .footer-container {	
      position: fixed;
      flex-direction:column;	
      width:100px;
      height: 600px;	
      top:400px;	
      left:0;	
      background-color: transparent;
  }
  .button{
    text-align: center;
    font-size: 40px;
    font-family: '微软雅黑';
    margin-top:40px;
    width:100px;
    height:100px;
    color:white;
    background: linear-gradient(rgb(50, 200, 150),rgb(0, 150, 120));
  }

</style>

其中floor数据可以存入后台的数据库,事先做一次查询的话,就实现了选择建筑物。
事实上,canvas还有三维的表现形式,理论上我们可以做出三维的动态地图。这个我们以后再说吧。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 `uni-app` 中,可以使用 `subnvue` 覆盖原生组件,实现更加灵活的布局和交互效果。具体步骤如下: 1. 在 `uni-app` 中添加 `subnvue` 组件,作为覆盖原生组件的容器。 ```vue <template> <div> <sub-nvue ref="subnvue"></sub-nvue> <button @click="showSubnvue">打开 subnvue 弹框</button> </div> </template> <script> export default { methods: { showSubnvue() { this.$refs.subnvue.show() } } } </script> ``` 2. 在 `sub-nvue.vue` 中定义覆盖原生组件的样式和内容。以覆盖 `uni-icon` 组件为例: ```vue <template> <div class="subnvue-container" v-show="show"> <div class="subnvue-content"> <uni-icon type="success" size="40"></uni-icon> <button @click="hide">关闭</button> </div> </div> </template> <script> export default { data() { return { show: false } }, mounted() { // 获取 uni-icon 组件 const uniIcon = uni.requireNativePlugin('uni-icon') // 设置 uni-icon 组件的自定义样式 uniIcon.setStyle({ 'success': { 'color': 'red' } }) }, methods: { show() { this.show = true }, hide() { this.show = false } } } </script> <style scoped> .subnvue-container { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; } .subnvue-content { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: #fff; padding: 20px; border-radius: 10px; text-align: center; } </style> ``` 通过 `uni.requireNativePlugin` 方法获取原生组件,并使用 `setStyle` 方法设置自定义样式,实现对原生组件的覆盖。在 `sub-nvue.vue` 中定义的样式也可以覆盖原生组件的默认样式。 这样就可以使用 `subnvue` 覆盖原生组件了,实现更加灵活的布局和交互效果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值