初衷
听说2018将是小程序爆发的一年,也许是随波追流吧,作为一名前端学习者,我也开始玩起了小程序,从原先在掘金看别人写的小程序项目,到如今不知不觉就自己倒腾了一个多月,也想做点东西练练手,于是有了这个小项目。
项目介绍
今年,青桔单车登录了我所在的城市,外形简约时尚,反正是特别喜欢。刚好我又在研究小程序,于是就想仿写一个青桔单车小程序的前端实现,大厂的项目还是很牛逼的,有不少提升体验的细节,值得学习,我也在其中注入了一些自己的想法,总体来说,我认为共享单车类小程序,其实还可以做得体验更好。
在这个项目过程中,踩了挺多坑的,很值得记录下来。于是行文,将我实现过程的种种,作为分享,希望能帮助到一些同学。
目录结构
●
┣━ config # 存放伪造数据的mock
┣━ images # 图片素材
┣━ libs # 引入的高德地图SDK
┣━ pages ● 页面
┣━ init //主界面
┣━ login //登录界面
┣━ userCenter //个人中心
┣━ messageCenter //消息中心
┣━ unlock //解锁
┣━ charge //计价
┣━ end //结束行程
┣━ repair //单车报修
┗━ record //行程记录
┣━ utils
┣━ app.js
┣━ app.wxss
┣━ app.json
┗━ project.config.json
具体内容
写一个不同设备上显示一致的地图主界面
主界面很简洁,上部分为一个map组件,下方为一个扫码解锁按钮,map组件中有三个小控件,一条横幅。看起来挺简单吧,但是想要做出这样的界面,得先稍思考一下。
map组件堪称小程序最复杂的一个组件,它是由客户端创建的原生组件,并且它的层级是最高的,不能通过 z-index 控制层级。这句话意味着,普通组件,无法覆盖在它的上方。不过cover-view 和 cover-image 组件例外,接下来就要用到。
更多细节可以查看map组件的官方文档map组件
使用弹性布局,安排上方地图,底部扫码解锁按钮
实测发现,青桔单车的底部按钮并非button组件,而是使用view组件 不过是添加了一些样式。底部按钮的高度是固定的,使用相对单位rpx。在不同的设备上,map组件高度要能跟据设备的屏幕高度自动拉伸或者收窄,而不影响到显示效果。使用弹性布局是最佳的解决方案,只要设置下方按钮flex:1,上方自适应即可。
使用cover-image打造体验和谐的控件
我们可以使用map组件得controls 做制作控件。也可以使用cover-image来制作。
体验了一下其他的地图类小程序,发现它们的控件使用了map组件的controls来制作,controls控件自带按压的交互效果,但是只能使用图片,无法设置样式,且它们的宽高大小单位默认是px,在不同设备上,实际体验很诡异。
青桔单车使用cover-image是来制作覆盖在地图表面的控件,在样式中通过rpx相对单位来设置控件大小,这能带来令人舒心的效果,不仅如此,这几天突然发现官方文档更新了,cover-image以后将完全代替controls
在map组件上做出阴影效果
在开发地图首页的过程中,最令我印象深刻的莫过于此了,毕竟这深深折磨过我好长一段时间。随意举两个例子,先来看看这些小程序中的效果。
有没有阴影效果,对于实际使用来说,完全没有任何影响,但也许这就是前端吧,即使在map组件上实现起来各种坑,前端开发者都得去想办法实现,正如雷布斯说过得那句话:因为我们是工程师。哪怕它的实际体验只能好百分之一,我们都会付出百分之九十九的努力。
前面有讲到map组件的层级最高。这也是最坑的一点。起初我天真地以为使用css box-shadow属性就能搞定,坑爹的开发者工具中确实也会显示出阴影效果,但是一到真机测试,所有的阴影都会被map组件覆盖,尝试了各种方法无果,而cover-view和cover-image能够支持的css样式又只有简单的几种,想要在map组件上使用css做出阴影效果基本上是不可能的。
目前解决方案只有一个,就是使用cover-image,添加一张能覆盖在map组件之上的图片来模拟阴影。实际上,这些大厂都是这样做的。
分析完了上述的问题,我们就能顺利做出这个主界面的效果,附上主页的wxml你就会明白怎么做了,样式具体实现方法可以查看我的源码
1 | <view class='map-box'> |
为地图添加定位功能
小程序为我们提供了很多好用的API,开发时可以去查看 小程序API
只需要调用一下 wx.getLocation(OBJECT) 这个API就可以很轻松地获取到当前所在位置
wx.getLocation({
type: 'gcj02',
success: (res) => {
let longitude = res.longitude;
let latitude = res.latitude;
this.setData({
longitude,
latitude
})
})
做出体验良好的地图交互
地图类小程序中,map组件上最主要地交互,莫过于重置定位这个按钮
重置定位功能实现起来很简单,只需要先创建一个map上下文,再调用moveToLocation()API就可以实现
onReady() {
// 创建map上下文 保存map信息的对象
this.mapCtx = wx.createMapContext('myMap');
}
在使用摩拜单车小程序地时候,如果缩放过地图视野,那么每次重置定位后,都要再去手动缩放地图寻找单车,因为单车扎堆在一起了
在青桔单车中,体验就好多了,重置定位后,也会重置地图视野地缩放级别,就能很快速判断附件单车位置,实现方法很简单,只需要在重置定位后设置1s后调回缩放比
toReset(){
//调回缩放比,提升体验
setTimeout(()=>{
this.setData({
scale: 18
})
},1000)
this.mapCtx.moveToLocation();
}
这也是一个小小的细节,地图类的小程序都可以用得上,实现的效果如下,这个体验很酷
写一个随机函数来生成伪造单车
为了实现一些更加高级的功能,我不得不做一些假数据,来模拟更加逼真的体验。
我简单的写了一个方法用来在当前定位的坐标点附件随机生成一批单车。
tocreate(res) {
// 随机单车数量设置 这里设置为1-20辆
let ran = Math.ceil(Math.random() * 20);
let markers = this.data.markers;
for(let i = 0; i < ran; i++) {
// 定义一个临时单车对象
var t_bic = {
"id": 0,
"title":'去这里',
"iconPath": "/images/map-bicycle.png",
"callout":{},
"latitude": 0,
"longitude": 0,
"width": 52.5,
"height": 30
}
// 随机
var sign_a = Math.random();
var sign_b = Math.random();
// 单车分布密集度设置
var a = (Math.ceil(Math.random() * 99)) * 0.00002;
var b = (Math.ceil(Math.random() * 99)) * 0.00002;
t_bic.id = i;
t_bic.longitude = (sign_a > 0.5 ? res.longitude + a : res.longitude - a);
t_bic.latitude = (sign_b > 0.5 ? res.latitude + b : res.latitude - b);
markers.push(t_bic);
}
//将模拟的单车数据暂时存储到本地
wx.setStorage({
key: 'bicycle',
data: markers
})
this.setData({
markers
})
}
接在来只要在map组件的bindregionchange事件中调用伪造单车的函数就行了
bindregionchange事件能在map视野发送变化时触发,但是我不希望地图稍作移动就会刷新单车,所以还需要简单模拟一下移动刷新单车的阈值
regionchange(e){
// 拿到起点经纬度
if(e.type == 'begin') {
this.mapCtx.getCenterLocation({
type: 'gcj02',
success: (res) => {
this.setData({
lastLongitude: res.longitude,
lastLatitude: res.latitude
})
}
})
}
// 拿到当前经纬度
if (e.type == 'end') {
this.mapCtx.getCenterLocation({
type: 'gcj02',
success: (res) => {
let lon_distance = res.longitude - this.data.lastLongitude;
let lat_distance = res.latitude - this.data.lastLatitude;
// console.log(lon_distance,lat_distance)
// 判断屏幕移动距离,如果超过设定的阈值,模拟刷新单车
if (Math.abs(lon_distance) >= 0.0035 || Math.abs(lat_distance) >= 0.0022){
console.log('刷新单车')
this.setData({
// 刷新单车之前先清空原来的单车
markers: []
})
this.tocreate(res)
}
}
})
}
}
这样,就做出了如下的效果
实现判断距离最近单车的功能
你们应该早就发现,地图上的单车中,距离最近的那辆单车头上会有离我最近
一个小气泡。 这个就是检索出最近的单车的功能,摩拜单车就实现了这个功能,可是青桔单车官方并没有加入这个小的体验,以后应该也会有吧。这里我尝试去实现了一下
实现逻辑
遍历当前地图上的每一辆单车和中心坐标点的距离,存到一个数组中
遍历数组,找出其中的最小值,并返回最小值的索引
在最小值的索引对应的单车中添加气泡提示
nearestBic(res) { // 找出最近的单车 let markers = this.data.markers; let min_index = 0, last_min_index = 0; let distanceArr = []; //存放单车距离的数组 for (let i = 0; i < markers.length; i++) { let lon = markers[i].longitude; let lat = markers[i].latitude; // 计算距离 sqrt((x1-x2)^2 + (y1-y2)^2 ) let t = Math.sqrt((lon - res.longitude) * (lon - res.longitude) + (lat - res.latitude) * (lat - res.latitude)); let distance = t; // 将每一次计算的距离加入数组 distanceArr distanceArr.push(distance) } //从距离数组中找出最小值 let min = distanceArr[0]; for (let i = 0; i < distanceArr.length; i++) { if (parseFloat(distanceArr[i]) < parseFloat(min)) { min = distanceArr[i]; min_index = i; } } let callout = "markers[" + min_index + "].callout"; // 清除旧的气泡,设置新气泡 wx.getStorage({ key: 'bicycle', success: (res) => { this.setData({ markers: res.data, [callout]: { "content": '离我最近', "color": "#ffffff", "fontSize": "16", "borderRadius": "50", "padding": "10", "bgColor": "#0082FCaa", "display": 'ALWAYS' } }) } }) }
将这个函数在每次刷新单车和map视野改变的时候调用,就能看到如下的效果了,详细调用过程请移步源码
实现手动选中单车自动规划步行至路径
嗯。。。这个功能我觉得还是有必要的,在一些场景中会遇到。
比如:我想骑车,眼前没有车。
或者只有一辆车,打开微信扫码,这时糟糕的结果出现了:该单车暂时无法使用。
我还是想骑车,不想走路,地图的功能就发挥作用了,我会查看地图附近别的单车,这时候看到了一些单车,但是得走一段路才能找到它,如果可以点一下这辆单车,就自动规划步行的路线就好了。
于是乎,我大胆地做了一个实现,如下图
接下来讲讲,怎么去实现它
想要实现自动路径规划的功能,自己去实现基本上不可能,我们需要借助第三方强大的力量来做到。
引入高德地图SDK
首先不知道你会不会这样想:What?腾讯地图里面用高德SDK?
这没有什么不可以的,在微信小程序中,不论是百度地图、高德地图、还是腾讯地图,都为小程序专门提供了Javascript SDK
高德地图微信小程序 SDK 能帮助我们在小程序中获取到丰富的地址描述、POI和实时天气数据,以及实现地址解析和逆地址解析等功能,非常强大,不过这里我们只需要使用到它路径规划的功能
腾讯地图和百度地图都没有为微信小程序提供自动路径规划的功能,所以高德地图还是很贴心的。
想要使用它,必须前往高德地图开放平台进行注册,获取到自己的key,详细的步骤在高德地图微信小程序SDK入门指南中介绍得很清楚
下载好后把它解压,在项目目录新建一个libs文件夹把它放进去
接着在需要用到得js文件顶部引入
var amapFile = require('../../libs/amap-wx.js');
var myAmapFun = new amapFile.AMapWX({ key: '你的key' });
有了它,就可以写一个专门负责规则路径得方法
route(bic){
// 获取当前中心经纬度
this.mapCtx.getCenterLocation({
success: (res) => {
// 调用高德地图步行路径规划API
myAmapFun.getWalkingRoute({
origin: `${res.longitude},${res.latitude}`,
destination: `${bic.longitude},${bic.latitude}`,
success: (data) => {
let points = [];
if (data.paths && data.paths[0] && data.paths[0].steps) {
let steps = data.paths[0].steps;
for (let i = 0; i < steps.length; i++) {
let poLen = steps[i].polyline.split(';');
for (let j = 0; j < poLen.length; j++) {
points.push({
longitude: parseFloat(poLen[j].split(',')[0]),
latitude: parseFloat(poLen[j].split(',')[1])
})
}
}
}
// 设置map组件polyline,绘制线路
this.setData({
polyline: [{
points: points,
color: "#ffffffaa",
arrowLine:true,
borderColor: "#3CBCA3",
borderWidth:2,
width: 5,
}]
});
}
})
}
})
}
微信小程序map组件提供了polyline属性,它能在map组件上方跟据设置好的点来绘制路径
路径的颜色和样式都可以设置,哇~简直有点酷
在这里,为了致敬青桔单车,我尽量的把路径的风格做得青桔单车相似