———— 润物无声,作一个有个格调的coderjavascript
小程序、快应用如今可谓是家喻户晓,也更加密切的渗透入咱们的生活中,笔者也算是个爱折腾的人,俗话说的好嘛,“不折腾,不前端“(固然是笔者本身的当心声)。因而在平日里忙碌的工做之余抽出来时间搞点事(si)情,来写一个属于本身的贴身小天气。说时迟那时快,这就来了...css
通过两三年的发展,小程序的地位也步步高升,由腾讯领队的腾讯小程序,再到后来的支付宝,美团,头条等也都相应推出自家的小程序平台,都想顺着潮流抓住流量分一杯羹,可谓是兵家必争之地。大环境的改变,为了提升小程序的快速迭代和多人合做开发的效率,也使得各大厂商都开源了本身的小程序框架,mpvue、wepy、MINA、Taro等相信你们也比较熟悉了。而小程序的社区也变得跟丰富健壮,也衍生出不少精美的UI框架。有兴趣的能够自行去相应的官网了解详情。html
虽然上面介绍了那么多的框架,而本次笔者并无使用框架,而是用原生的小程序来开发今天的主角,也但愿可以用原始的方式来给那些和笔者同样的刚刚入门的小程序开发者一些帮助,也将本身所学的记录下来。毕竟原生才是最底层的基础,全部的框架都是在原生的基础上开花结果的,这样才能以不变应万变(吼吼~)。笔者水平有限,有错误或者解释不当的地方还望各位看官多多包涵。前端
项目源码 润物无声githubvue
在定位功能中,本程序用到腾讯地图的api
相应的天气接口中,本程序用到的是和风天气提供的apijava
二者都须要到官网中注册开发者帐号,经过注册后获得的appKey来请求咱们须要的数据,详细注册步骤请自行度娘git
因为须要用到定位功能,而小程序自己的getLocation方法获取到的是当前位置的坐标:github
wx.getLocation({ type: 'gcj02', // 返回坐标的格式 success: res => { // 此处只能获取到当前位置的经纬度(坐标) }, })
因此须要利用腾讯地图Api,经过坐标点反向得到该地点的详细信息。json
在app.json中是对整个小程序的一些基本配置canvas
{ "pages": [ "pages/index/index" // 当前小程序的主入口页面 ], // 主窗口的一些配置,以下,对背景颜色和小程序顶部的导航栏的样式进行了配置 "window": { "backgroundColor": "#A7CAD3", "backgroundTextStyle": "dark", "navigationBarBackgroundColor": "#A7CAD3", "navigationBarTitleText": "蜗牛天气", "navigationBarTextStyle": "black", "navigationStyle":"custom" }, "permission": { "scope.userLocation": { "desc": "蜗牛天气尝试获取您的位置信息" // 询问用户是否能够获得获取位置权限的提示文字 } } }
接下来,咱们就来一步一步的实现这个小程序吧~~
因为没有UI,再加上笔者扭曲的审美能力(坐在屏幕前开始愣神,陷入沉思...),因此还望各位看官多忍耐笔者又想又借鉴的界面成果...看来之后要多增强这方面的能力(haha~)
好了言归正传,首先,准备用一个页面来解决战斗,那就是各位看到以上的这个页面(都说了是‘小天气’嘛),页面一共分为五个部分,实时天气、24小时内天气状况、将来一星期内天气状况、今天日落日出风向降雨等相关信息和天气的生活指数,这五个部分组成了整个页面,其对应的相应布局见一下代码
<view class="container" style='background: url({{bgImgUrl}}) center -200rpx / 100% no-repeat #62aadc;'> <view style='margin-top: -150rpx; padding-top: 150rpx;background: rgba(52,73,94, {{apl}})'> <view class='animation-view'> <!-- 当前定位信息显示 --> <view class='location' bind:tap="chooseLocation"> <myicon class="icon" type="dingwei"></myicon> <text class='city'>{{position}}</text> </view> <!-- 经过canvas画出当前天气状况动画 --> <canvas canvas-id='animation' ></canvas> <!-- 实时天气状况 --> <view class="center-container"> ... </view> <!-- 24小时内天气状况 --> <view class="all-day-list"> <scroll-view class="scroll-view_x" scroll-x> <view class="all-day-list-item" wx:for="{{everyHourData}}" wx:key="item.time"> <view class="day-list-item">{{item.time}}点</view> <view class="day-list-item"> <myicon type="{{item.iconType}}"></myicon> </view> <view class="day-list-item">{{item.tmp}}°</view> </view> </scroll-view> </view> </view> <!-- 一星期内天气 --> <view class="one-week-list"> <view class="one-week-list-item" wx:for="{{everyWeekData}}" wx:key="item.weekday"> <view class="week-list-item"> <view>{{item.weekday}}</view> <view>{{item.date}}</view> </view> <view class="week-list-item">{{item.cond_txt_d}}</view> <view class="week-list-item"> <myicon type="{{item.iconTypeBai}}"></myicon> </view> <view class="week-list-item">{{item.tmp_min}}~{{item.tmp_max}}°</view> <view class="week-list-item"> <myicon type="{{item.iconTypeYe}}"></myicon> </view> <view class="week-list-item">{{item.cond_txt_n}}</view> <view class="week-list-item" style="font-size: 28rpx"> <view>{{item.wind_dir == '无持续风向' ? '无' : item.wind_dir}}</view> <view>{{item.wind_sc}}级</view> </view> </view> </view> <!-- 日出日落风向降雨几率等 --> <view class='live-index-view'> ... </view> <!-- 生活指数 --> <view class='last-view'> <view class='last-view-item' wx:for="{{lifeStyle}}" wx:key="item.type"> <view class='last-view-item-top'>{{lifeEnum[item.type]}}</view> <view class='last-view-item-bottom'>{{item.brf}}</view> </view> </view> </view> </view>
具体 css 样式,详见 蜗牛小天气 源码
注意:(笔者入坑,一开始使用的纵向的scroll-view,后来无奈的用了原来页面的滚动)
scroll-view: 具体属性参考小程序官方文档
在小程序中,内部为咱们提供了scroll-view这个页面滚动的组件,对性能进行了一些优化,方便咱们的使用。与此同时,也会有一些小坑
- 在使用scroll-view是,若是是纵向(Y轴)滚动,scroll-y属性,则必须为此scroll-view设置一个固定(明确)的高
- 请勿在scroll-view组件标签内使用 textarea、map、canvas、video等组件
- 在使用了scroll-view组件时会阻止页面的回弹效果,也就是在scroll-view中滚动,没法触发onPullDownRefresh方法
- 若是想使用原生的下拉刷新(非自定义)或者双击顶部页面回滚到页面顶部,请不要使用scroll-view。
相信各位看官发现了以上代码中有一个<myicon> 的标签,此标签为一个图标组件,由于蜗牛天气中用到的图标也比较多。接下来咱们说明下有关组件的封装事宜。为了提升代码的复用性和易维护性,以及多人合做开发的效率,组件化彷佛是一个很好的解决办法,在微信小程序中,每一个页面由四个文件组成
而对于本小程序中<myicon>组件来讲
<!-- 仅有一个text标签,经过type属性来改变字符图标的类型(多云,小雨...) --> <!-- 字符图标经过css样式和自定义字体来实现,经过class来显示不一样类型的图标字体 --> <!-- 具体自定义字体图标制做过程可参考此连接 https://blog.csdn.net/thatway_wp/article/details/79076023 --> <text class="icon icon-{{type}}"></text>
// 小程序中的组件,经过调用Component方法,将组件的逻辑处理部分,属性以及方法(生命周期)等一对象的方式传入Component方法中 Component({ properties: { type: { type: String, // type属性的类型 value: '' // 默认值 } } });
使用了Component构造器,经过参数指定组件的属性,数据,方法以及生命周期中的一些方法,在此组件中定义了接受的type属性,类型为字符串,其默认值为空字符串。
{ "component": true // 配置,当前为组件 }
// CSS 部分略过...
就这样,一个简单的icon图标组件就封装好了,是否是很简单啊。封装是封装好了,那么咱们怎么调用这个组件呢,是否是很相似于Vue呢,没错,只须要在你调用的页面中注册一下便可
// 当前想要调用的页面的*.json文件中,以下 { "enablePullDownRefresh": true, // 此项与组件无关,此项为是否用小程序自己的下拉刷新功能 "usingComponents": { "myicon": "../../components/icon/index" // 调用,注册icon组件 } }
一开始就说到了须要使用腾讯地图API的appkey还有和风天气API的appkey,笔者是将appkey配置在了config.js中,看官只需将本身相应的appkey值替换便可,因为appkey是私密的,此处就不公开了,还望谅解。
// config.js export default { MAP_API_KEY: 'XXXXX-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX', // 腾讯地图key WEATHER_API_KEY: 'XXXXXXXXXXXXXXXX' // 和风天气key }
全部数据的接口,都定义在了api.js文件中,此处没什么好说的,看官自行经过接口文档查询。接口均采用回调的方式,笔者并无封装成Promise的方式,若是有兴趣可自行更改。
// 引入config,为了后面的key import config from '../uitl/config' // 地图key const mapKey = config.MAP_API_KEY // 和风天气key const weatherKey = config.WEATHER_API_KEY // map url const locationUrl = 'https://apis.map.qq.com/ws/geocoder/v1/' //天气url const weatherUrl = 'https://free-api.heweather.net/s6/weather/forecast' //24小时内 每小时 const everyhoursUrl = 'https://api.heweather.net/s6/weather/hourly' // 一周内 const everyWeekUrl = 'https://api.heweather.net/s6/weather/forecast' //空气质量 const airQualityUrl = 'https://api.heweather.net/s6/air/now' // 实况天气 const weatherLive = 'https://api.heweather.net/s6/weather/now' // 生活指数 const lifeStyle = 'https://api.heweather.net/s6/weather/lifestyle' // 根据当前位置的坐标反获得当前位置的详细信息 // lat,lon 为经纬度坐标 export const getPosition = (lat, lon, success = {}, fail = {}) => { return wx.request({ url: locationUrl, header: { 'Content-Type': 'application/json' }, data: { location: `${lat},${lon}`, key: mapKey, get_poi: 0 }, success, fail }) } // 根据location获得天气信息 // lat,lon 为经纬度坐标 export const getWeaterInfo = (lat, lon, success = {}, fail = {}) => { return wx.request({ url: weatherUrl, header: { 'Content-Type': 'application/json' }, data: { location: `${lat},${lon}`, lang: 'zh', unit: 'm', key: weatherKey }, success, fail }) } // 根据location信息获得24小逐小时天气状况 // lat,lon 为经纬度坐标 export const getEveryHoursWeather = (lat, lon, success = {}, fail = {}) => { return wx.request({ url: everyhoursUrl, header: { 'Content-Type': 'application/json' }, data: { location: `${lat},${lon}`, lang: 'zh', unit: 'm', key: weatherKey }, success, fail }) } ... ... // 其余接口相似 ... }
首先,当初次加载页面时,大致流程为首先经过定位获取位置,而后经过位置信息去获得咱们须要的每一项天气信息,最后将天气信息渲染后页面中相应的位置
具体流程:
获取生活指数信息
当经过手动改变位置信息时,按顺序重复执行以上步骤
经过wx.getLocation原生方法获取经纬度信息,在通过腾讯地图api经过经纬度逆向获取到相应的位置信息,对于这个项目来讲获取位置信息是最重要的信息,故咱们但愿在页面一加载的时候就执行方法获取,而后『onLoad』方法能够帮助咱们解决,这个方法就是小程序的生命周期函数--监听页面加载,此方法会在页面刚加载的时候(document文档结构渲染完成后)执行。
小程序页面(Page)的生命周期函数:
name | type | functional |
---|---|---|
onLoad | 函数 | 监听页面加载 |
onReady | 函数 | 监听页面初次渲染完成 |
onShow | 函数 | 监听页面显示 |
onHide | 函数 | 监听页面隐藏 |
onUnload | 函数 | 监听页面卸载 |
如下为获取位置信息代码:
// onLoad onLoad: function () { ... ... this.getPositon() // 调用获取位置信息 } // 原生方法获取经纬度信息 getPosition: function () { wx.getLocation({ type: 'gcj02', success: this.updateLocation, // 成功会掉 updataLocation 方法为更新位置 fail: err => { console.log(err) } }) } // 更新位置信息 updateLocation: function(res) { ... ... let {latitude: x,longitude: y,name} = res; let data = { location: { x, y, name: name || '北京市' }, ... ... }; this.setData(data); // 设置page中data对象中的属性 // 经过经纬度逆向得到位置信息 this.getLocation(x, y, name); } // 逆向获取位置信息 getLocation: function(lat, lon, name) { wx.showLoading({ title: "定位中", mask: true }) // 腾讯地图api接口 getPosition(lat, lon, (res) => { if (res.statusCode == 200) { let response = res.data.result let addr = response.formatted_addresses.recommend || response.rough this.setData({ position: addr // 赋值给 data对象中的相应属性 }) wx.hideLoading() this.getData(lat, lon); } }, (err => { console.log(err) wx.hideLoading() })) }, // 当用户点击显示定位处的view时,会调用原生的chooseLocation方法,内部调用选择位置页面 chooseLocation: function() { wx.chooseLocation({ success: res => { let {latitude,longitude} = res let {x,y} = this.data.location if (latitude == x && longitude == y) { } else { this.updateLocation(res) } } }) },
上面代码中两次用到了setData方法,该方法接受一个对象,对象中的属性为须要改变的数据,同时接受一个callback函数,用于经过改变数据更新页面渲染完成以后的回调。咱们来看看data的做用。
page({ data: { backgroundColor:'red', fontSize: '20', ... ... } })
在page中,data中的属性是链接逻辑层和视图层的一个桥梁,也就是说咱们能够经过js代码的逻辑来控制data中的属性的值,而页面中的一部分显示内容是根据data中的属性的值而变化。这也就是咱们所说的mvvm模型,咱们只需把重心放在js逻辑层,而无需去频繁的手动的操做视图层。了解了data的做用,再来讲setData,setData就是在js逻辑层中去改变和设置data中的属性的值,从而使页面获得响应。
... this.setData({ backgroundColor: 'green' // 改变背景颜色属性,视图中以来此属性的会将颜色变成绿色 }) ...
注意:
- 直接修改this.data的值,而不是经过调用this.setData()方法,是没法成功改变页面的状态的
- 仅仅支持JSON化的数据(key:value)
- 单词设置的值不能超过1024K,因此使用的时候尽可能不要一次设置过多的数据
- 不要把data中的任何一项value值设置成undefined,不然这一项将不能被设置,也可能会有其余问题
- 不要频繁的去调用this.setData()方法去设置同一个值或者多个值,好比经过在循环中调用this.setData(),这样会致使性能损耗
经过上面得到到的位置信息,用来调用相应的接口得到当前位置的天气。方法接口已在前面封装好,直接调用而后经过对response进行过滤或者重组等来知足当前的应用,最后经过this.setData()方法去更新数据是页面获得响应。
- getWeather(lat, lon) // lat, lon 为当前位置的经纬度
- getAir(lat, lon)
- getHourWeather(lat, lon)
- getWeatherForWeek(lat, lon)
- getLifeIndex(lat, lon)
以上方法不在一一列举其中数据处理的过程,可自行查看源码 详见 蜗牛小天气 源码
粒子动画在如今愈来愈多的项目中被用到。从静态到动态最后再到仿真效果更好的视觉体验,也是人们在视觉上追求极致的体验。咱们经过粒子,也就是经过点和线,来模拟出雨和雪的效果。经过小程序中的canvas画布来画出咱们想要的效果。
实现原理:
Weather类是一个基类,主要处理画布的一些信息,例如width,height,定时器,以及当前动画的状态(status)等
const STOP_ANIMATION = 'stop' const START_ANIMATION = 'start' class Weather { constructor(context, width, height, option = {}) { this.opt = option || {} this.context = context this.timer = null this.status = STOP_ANIMATION this.width = width this.height = height this._init() } // 实例调用此方法,开始在画布上画 start() { if(this.status !== START_ANIMATION) { this.status = START_ANIMATION this.timer = setInterval(() => { this._drawing() }, 30) return this } } stop() { this.status = STOP_ANIMATION clearInterval(this.timer) this.timer = null return this } } export default Weather
Rain类继承自Weather类,经过_init方法和父类中画布参数,以及option参数中的counts(雨滴数量)来初始化。
import Weather from './Weather.js' class Rain extends Weather { // 初始化 _init() { this.context.setLineWidth(2) this.context.setLineCap('round') let height = this.height let width = this.width let counts = this.opt.counts || 100 let speedCoefficient = this.opt.speedCoefficient let speed = speedCoefficient * height this.animationArray = [] let arr = this.animationArray for (let i = 0; i < counts; i++) { let d = { x: Math.random() * width, y: Math.random() * height, len: 2 * Math.random(), xs: -1, ys: 10 * Math.random() + speed, color: 'rgba(255,255,255,0.1)' } arr.push(d) } } // 开始画 _drawing() { let arr = this.animationArray let ctx = this.context ctx.clearRect(0, 0, this.width, this.height) for (let i = 0; i < arr.length; i++) { let s = arr[i] ctx.beginPath() ctx.moveTo(s.x, s.y) ctx.lineTo(s.x + s.len * s.xs, s.y + s.len * s.ys) ctx.setStrokeStyle(s.color) ctx.stroke() } ctx.draw() return this.update() } // 更新画布 update() { let width = this.width let height = this.height let arr = this.animationArray for (let i = 0; i < arr.length; i++) { let s = arr[i] s.x = s.x + s.xs s.y = s.y + s.ys if (s.x > width || s.y > height) { s.x = Math.random() * width s.y = -10 } } } } export default Rain
Snow类继承自Weather类,经过_init方法和父类中画布参数,以及option参数中的counts(雪花数量)来初始化。
import Weather from './Weather.js' class Snow extends Weather { // 初始化 _init() { let { width, height } = this console.log(width) let colors = this.opt.colors || ['#ccc', '#eee', '#fff', '#ddd'] let counts = this.opt.counts || 100 let speedCoefficient = this.opt.speedCoefficient || 0.03 let speed = speedCoefficient * height * 0.15 let radius = this.opt.radius || 2 this.animationArray = [] let arr = this.animationArray for (let i = 0; i < counts; i++) { arr.push({ x: Math.random() * width, y: Math.random() * height, ox: Math.random() * width, ys: Math.random() + speed, r: Math.floor(Math.random() * (radius + 0.5) + 0.5), color: colors[Math.floor(Math.random() * colors.length)], rs: Math.random() * 80 }) } console.log(arr) } // 开始画 _drawing() { let arr = this.animationArray let context = this.context context.clearRect(0, 0, this.width, this.height) for (let i = 0; i < arr.length; i++) { let { x, y, r, color } = arr[i] context.beginPath() context.arc(x, y, r, 0, Math.PI * 2, false) context.setFillStyle(color) context.fill() context.closePath() } context.draw() this._update() } // 更新画布 _update() { let { width, height } = this let arr = this.animationArray let v = this.opt.speedCoefficient / 10 for (let i = 0; i < arr.length; i++) { let p = arr[i] let { ox, ys } = p p.rs += v p.x = ox + Math.cos(p.rs) * width / 2 p.y += ys if (p.x > width || p.y > height) { p.x = Math.random() * width p.y = -10 } } } } export default Snow
结束!!!
到此,蜗牛小天气就开发完成了,但愿对各位有帮助。 但愿在阅读的同时若是感受对各位有帮助,还请看官别忘了给你大大的赞👍,码字不易... 初来乍到,能耐有限,水平通常,只是想着用文字来记录和传递一些能量。若是文中有错误之处,还请各位多多提意见,共同探讨,也请各位多包涵。