内容简介:前段时间学习了关于微信小程序的开发,光说不练假把式,所以就打算自己手撸一个微信小程序,而网上电商类小程序太多了,所以就选择了旅游攻略类小程序来练手。这是我第一次写小程序和第一次写文章,不足之处请多包涵,谢谢。下面我会分享我在写小程序的时候遇到的问题和获得的经验,希望能给你带来帮助,也欢迎大佬指正。最后,我要感谢我在写小程序的时候给我帮助的老师和同学,还有百度上所有给过我帮助的有名的无名的作者。我的废话说完了,先上项目效果图。(链接中详细介绍了关于自定义组件的使用方法)必须把app.json中window属性
前段时间学习了关于微信小程序的开发,光说不练假把式,所以就打算自己手撸一个微信小程序,而网上电商类小程序太多了,所以就选择了旅游攻略类小程序来练手。这是我第一次写小程序和第一次写文章,不足之处请多包涵,谢谢。下面我会分享我在写小程序的时候遇到的问题和获得的经验,希望能给你带来帮助,也欢迎大佬指正。最后,我要感谢我在写小程序的时候给我帮助的老师和同学,还有百度上所有给过我帮助的有名的无名的作者。我的废话说完了,先上项目效果图。
开发前的准备
项目的所有页面
自定义顶部导航栏组件
微信小程序自带的顶部导航栏 满足不了实际的需求所以就自己写了一个组件,顶部导航栏要想使用 自定义组件(链接中详细介绍了关于自定义组件的使用方法)必须把app.json中window属性设置为:
"window": { "navigationBarTextStyle": "black",//导航栏标题颜色,仅支持 black / white "navigationStyle": "custom" //导航栏样式,仅支持以下值:default 默认样式custom 自定义导航栏,只保留右上角胶囊按钮 } 复制代码
wxml
<view class='nav-wrap' style='height: {{height*2 + 20}}px; background-color:{{navbarData.backgroundColor}};opacity:{{navbarData.opacity}}'> <view style="width:100%;height:100%;"> <!--城市名--> <navigator url="/pages/destination/destination" hover-class="none"> <view class="nav-city" style='margin-top:{{height*2 + 20-36}}px;' wx:if='{{navbarData.showMain}}'> <text>{{navbarData.cityName}}</text> <view class="downtips"></view> </view> </navigator> <navigator url="/pages/search/search" hover-class="none"> <!--搜索框--> <view class="section" style='top:{{height*2 + 20-34}}px;' wx:if='{{navbarData.showMain}}'> // 这里的搜索框不是一个input组件,只是一个view可供点击然后跳到搜索页 <view class='search_icon'> <icon type='search' size='14px'></icon> </view> <view class='placehold'>搜索目的地/景点/攻略</view> </view> </navigator> </view> <!-- 标题 --> <view wx:if="{{navbarData.title!=''}}" class='nav-title' style='line-height: {{height*2 + 44}}px;'> {{navbarData.title}} </view> <!-- 返回上一级按钮 和 返回主页按钮--> <block wx:if="{{navbarData.showCapsule===1}}"> <view class='nav'> <view class='nav_back' bindtap="_navback"> <image src='/images/back.png'></image> </view> <view class="line"></view> <view class='nav_home' bindtap="_backhome"> <image src='/images/home.png'></image> </view> </view> </block> </view> 复制代码
组件中的元素都可以通过当前页面传入组件的数据控制显示与否
js就写了两个路由跳转 函数,微信小程序官方文档有很详细的介绍,这里就不多赘述了。
登录界面
初进小程序,会跳到登录授权页面,因为微信小程序不再支持 wx.getUserInfo接口直接弹出授权框的开发方式,所以这里直接使用 button 组件,并将 open-type 指定为 getUserInfo 类型,获取用户基本信息。
<button style='background:green; color:#fff' open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">同意授权</button 复制代码
小程序在授权允许访问用户信息后,又会弹出位置授权框用来获取用户当前所在地,来渲染主页面的数据。调用小程序给的接口wx.getLocation(需要用户授权) 来获取经纬度,再把获取到的经纬度利用百度地图开放平台 提供给小程序使用的API来获取当前城市的名字,并将城市名字放入缓存,好让主页面获取到。
##注意: 使用wx.getLocation()需要在app.json中配置
"permission": { "scope.userLocation": { "desc": "小程序将获取你的位置信息" } } 复制代码
登录界面js
// miniprogram/pages/login/login.js const app = getApp() Page({ /** * 页面的初始数据 */ data: { show: false, // 顶部导航栏数据 navbarData: { showCapsule: 0, //是否显示左上角图标 1表示显示 0表示不显示 title: '马蜂窝旅游', //导航栏 中间的标题 backgroundColor: '#354a98', //'#354a98' opacity: 1, showMain: 0, }, // 此页面 页面内容距最顶部的距离 height: app.globalData.height * 2 + 20, }, bindGetUserInfo(res) { let that =this let info = res; if (info.detail.userInfo) { wx.login({ success: function (res) { that.getPlaceData() } }) } }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { let that = this; //页面加载时判断用户是否授权过,如果授权过直接跳到主页面,没有就显示授权按钮 wx.getUserInfo({ success: function (res) { wx.switchTab({ url: '/pages/main/index' }) }, fail(err) { that.setData({ show: true }) } }) }, // 获取城市名字 getCityName(location) { return new Promise((resolve, reject) => { let that = this; var e = { coord_type: "gcj02", output: "json", pois: 0, ak: '',//放上自己的ak密钥 密钥申请见上文百度地图开方平台链接 sn: "", timestamp: "" }; e.location = location; wx.request({ url: "https://api.map.baidu.com/geocoder/v2/", data: e, header: { "content-type": "application/json" }, method: "GET", success: function (t) { let currentCity = t.data.result.addressComponent.city; if (currentCity.slice(currentCity.length - 1) == "市") { currentCity = currentCity.slice(0, currentCity.length - 1) } wx.setStorageSync('currentCity', currentCity) resolve(currentCity) //通过城市名字 请求城市数据 } }) }) }, // 获取经纬度 getLocation() { return new Promise((resolve, reject) => { wx.getLocation({ type: 'wgs84', success(res) { const latitude = res.latitude const longitude = res.longitude let location = latitude + ',' + longitude console.log(location) resolve(location) //获取城市名字 } }) }) }, getPlaceData() { // 获取地理信息 let that = this this.getLocation().then((val) => { return that.getCityName(val) }).then(()=>{ wx.switchTab({ url: '/pages/main/index' }) }) } }) 复制代码
主页面
写小程序的时候我不知道主页面有两种样式,等我知道的时候已经写了不少东西了,所以就没有写成组件了,代码看起来就很冗长,这是我的失误(MangFu),希望你在想好写什么小程序的时候,一定要把小程序的页面结构想好来否则就会和我一样,要改的话就要改很多地方。
- 普通城市页面
- 热门城市页面
进入主页是,页面会先获取到缓存中的城市名字,再通过城市名字去请求数据,再根据请求到的数据中的ishot属性,如果ishot属性为真,就显示热门城市的页面 ,反之就显示普通城市的页面
‘我的’页面
‘我的’页面中主要是为了显示用户收藏的内容
景点详情页
因为种种原因(lan)页面中的大半数据没有放到Easy Mock里,马蜂窝本来就以大数据出名,数据ttm多了。
洲/国家/城市列表页
这个页面的布局分为三部分,头部搜索框用绝对定位定死、左部各大洲的列表用绝对定位定死,右部各大洲的国家是一个微信小程序自带的组件scroll-view
wxml
<!-- pages/destination/destination.wxml --> <nav-bar navbar-data='{{navbarData}}'></nav-bar> <view class="destination" style='top: {{height}}px'> <!--头部--> <view class="des_head"> <navigator url="/pages/search/search" hover-class="none"> <view class="des_search"> <view class="des_search_icon"> <icon type='search' size='30rpx' color="#000000"></icon> </view> 搜索目的地 </view> </navigator> </view> <!--左部--> <view class="des_continents"> <view class="des_continent {{curIndex===index?'add':''}}}" wx:for="{{continents}}" wx:for-item="continent" wx:key='{{index}}' data-index='{{index}}' bindtap="switch_des"> <view class='des_continent_name {{curIndex===index?"on":""}}}'>{{continent.name}}</view> </view> </view> <!--右部--> <scroll-view class='des_cities' scroll-y> <block wx:if="{{curIndex==0}}"> <view class="des_cities_content" wx:for="{{continents[curIndex].cities}}" wx:key="{{index}}" wx:for-item="des_city"> <view class="des_cities_title">{{des_city.title}}</view> <view class="des_city" wx:for="{{des_city.city}}" wx:key="{{index}}" bindtap='goMain' data-city_name="{{item.city_name}}"> {{item.city_name}} </view> </view> </block> <block wx:else> <view class="des_area" wx:for="{{continents[curIndex].cities}}" wx:key="{{index}}" wx:for-item="des_city" bindtap='goMain' data-city_name="{{des_city.city_name}}"> <view class="des_img"> <image src="{{des_city.img}}" /> </view> <view class="des_city_name">{{des_city.city_name}}</view> </view> </block> </scroll-view> </view> 复制代码
js
// pages/destination/destination.js const app = getApp() Page({ /** * 页面的初始数据 */ data: { <!--顶部导航栏数据--> navbarData: { showCapsule: 1, //是否显示左上角图标 1表示显示 0表示不显示 title: '目的地切换', //导航栏 中间的标题 backgroundColor: '#fff',//背景颜色 showMain: 0 ///显示搜索框 }, height: app.globalData.height * 2 + 20, continents: [], curIndex: 0 //当前洲的索引值 }, <!--左部各大洲的点击事件,来改变右边显示的内容,并且改变自身样式--> switch_des(e) { let curIndex = e.currentTarget.dataset.index; this.setData({ curIndex, }) }, <!--右部国家/城市的点击事件,获取点击的元素上绑定的国家/城市的名字,放入缓存,并跳转到主页--> goMain(e){ const city_name = e.currentTarget.dataset.city_name; wx.setStorageSync('currentCity', city_name) wx.switchTab({ url: '/pages/main/index' }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { let that = this <!--请求数据--> wx.request({ url: 'https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/continents', success:(res)=>{ that.setData({ continents: res.data.continents }) } }) } } 复制代码
搜索页
实现的功能
点击切换列表
以主页为例
其实所有的切换列表功能都差不多,实现方法就是在被点击元素上设置一个自定义属性 ( data-* ) 为唯一索引值,用bind-tap 绑定一个点击事件,通过点击事件获取这个唯一索引值,再通过唯一索引值去数据源找到想要的内容,然后通过数据控制页面上显示的内容,在data数据源中设置一个数据如mcurIndex,表示当前选择的元素,用来区别于其他元素,显示不同的样式。
wxml
<view class='menu_list'> <!-- {{mcurIndex===index?"on":""}} 表示如果自身的索引值为当前选择的元素索引值时,添加一个类名‘on’--> <view class='list {{mcurIndex===index?"on":""}}' wx:for="{{placeData.allGuide}}" data-mindex="{{index}}" bindtap='selected_menu' wx:key="{{index}}"> {{item.name}} </view> </view> 复制代码
js
selected_menu(e) { this.setData({ mcurIndex: e.target.dataset.mindex, size: 0, showend: false }) <!--调用自己写的函数来获取要显示的内容的数据--> this.bitiyan() } 复制代码
滑动页面改变顶部导航栏的可见度和上拉加载
-
滑动页面改变顶部导航栏的可见度
以主页为例
组件,组件中有个 bindscroll 属性,会在页面滚动时触发bindscroll 绑定的事件还会给函数传递一个对象event,其中的scrollTop属性是我们需要的,根据scrollTop知道页面滚动了多少,然后动态设置要传给组件的数据里的 opacity 属性。
<scroll-view class="main_scro" scroll-y bindscroll="scroll" bindscrolltolower="bindDownLoad"> </scroll-view> 复制代码
js
scroll(e) { let opacity = 0; if (e.detail.scrollTop < 60) { opacity = (e.detail.scrollTop / 100).toFixed(1); } else { opacity = 1; } this.data.navbarData.opacity = opacity; if (e.detail.scrollTop<10){ this.setData({ shownav: false }) }else{ this.setData({ shownav: true }) } this.setData({ navbarData: this.data.navbarData, }) } 复制代码
-
上拉加载
以主页为例
这里的实现方法在scroll-view 组件中加 bindscrolltolower 属性,会在页面触底时触发bindscrolltolower 绑定的事件。
<scroll-view class="main_scro" scroll-y bindscroll="scroll" bindscrolltolower="bindDownLoad"> </scroll-view> 复制代码
bindDownLoad() { let part = 0; //已经显示的数据长度 let all = 0; //总的数据长度 <!--判断当前城市是否为热门城市--> if (this.data.ishot) { // 待完善 因为效果相同就没写了 } else { if (this.data.mcurIndex === 0) { part = this.data.cur_view.length * 2; all = this.data.placeData.allGuide[this.data.mcurIndex].content[this.data.hlcurIndex].content.length; } else { part = this.data.cur_view.length; all = this.data.placeData.allGuide[this.data.mcurIndex].content.length; } if (part < all) { wx.showLoading({ title: '正在加载' }) setTimeout(() => { this.bitiyan(this.data.placeData) wx.hideLoading() }, 1000) } else { <!--当所有数据都加载完了,就显示end 图标--> this.setData({ showend: true }) } } } 复制代码
关于scroll-view组件有几点需要注意的
- 设置竖向滚动的时后一定要设高度,有时候会发现设置了高度100%后,当滑到底部的时候,会显示不完整,这时候要看下你是否设置了margin/padding,或者父元素设置了margin/padding,这时的scroll-view组件的高度就要减去相应的margin/padding
- 当设置为横向滚动时需要注意,scroll-view 中需要滑动的元素不可以用 float 浮动;scroll-view 中的包裹需要滑动的元素的大盒子用 display:flex 是没有作用的;scroll-view 中的需要滑动的元素要用 dislay:inline-block 进行元素的横向编排;包裹 scroll-view 的大盒子有明确的宽和加上样式--> overflow:hidden;white-space:nowrap;
收藏功能
收藏功能我是写在一个组件里,本来是想和顶部组件一样,供多个页面使用,后来因为写的页面中就只有一个有用到这个组件,这里就不单独说明这个组件了,而且这个组件和顶部组件基本差不多。
收藏功能的实现,当点击某一个景点时会触发点击事件,相信你看了列表切换功能,已经知道了bind-tap 的使用方法,这里就不重复了。这里就是获取元素上的自定义属性,通过路由传参的方法传给详情页,详情页根据传递过来的数据,去数据源里获取相应的数据,再将数据传递给组件,当点击详情页上的收藏按钮时,会触发绑定的事件,然后会更新缓存中的collectData收藏夹数据。‘我的’页面会显示收藏夹中的数据
详情页js
<!--生命周期函数,监听页面加载--> onLoad: function(options) { <!--options中包含了传递过来的参数--> let name = options.name; this.getinfo(name) }, <!--通过名字获取想要的数据--> getinfo(name){ <!--先获取缓存中已经存在的收藏夹数据,如果不存在就将collectData设为空数组--> let collectData = wx.getStorageSync('collectData') || []; if (collectData.filter(e => e.name === name).length > 0) { this.setData({ placeData: collectData.filter(e => e.name === name)[0] }) } else { let placeData = wx.getStorageSync('placeData') let view = placeData.allGuide[0].content.map(e => e.content) let newView = [] for (let i = 0; i < view.length; i++) { newView.push(...view[i]) } this.setData({ placeData: newView.find(e => e.name === name) }) } this.setBottom(); }, <!--设置要传递给bottom组件的数据--> setBottom(){ this.data.bottomData.placeData = this.data.placeData; let bottomData = this.data.bottomData; this.setData({ bottomData }) } 复制代码
bottom组件的js
// components/bottom/bottom.js const app = getApp() Component({ /** * 组件的属性列表 */ properties: { bottomData: { // 由父页面传递的数据,变量名字自命名 type: Object, value: {}, observer: function (newVal, oldVal) { } } }, /** * 组件的初始数据 */ data: { height: '' }, attached: function () { // 获取是否是通过分享进入的小程序 this.setData({ share: app.globalData.share }) // 定义导航栏的高度 方便对齐 this.setData({ height: app.globalData.height }) }, /** * 组件的方法列表 */ methods: { <!--点击收藏按钮触发的事件--> collected(){ <!--将isCollect(是否收藏过),collectors(收藏人数)从数据中解构出来--> let {isCollect,collectors} = this.data.bottomData.placeData; isCollect = !isCollect; this.data.bottomData.placeData.isCollect = isCollect; let collectData = wx.getStorageSync('collectData') || []; if(isCollect){ wx.showToast({ title: '收藏成功', icon: 'success', duration: 2000 }) collectors++; collectData.push(this.data.bottomData.placeData); }else{ wx.showToast({ title: '已取消收藏', icon: 'success', duration: 2000 }) collectors--; collectData = collectData.filter(e => e.name != this.data.bottomData.placeData.name) } this.data.bottomData.placeData.collectors = collectors; <!--将收藏夹数据放入缓存--> wx.setStorageSync('collectData', collectData) let bottomData = this.data.bottomData; this.setData({ bottomData }) } } }) 复制代码
搜索功能
效果一
效果二
搜索功能的实现是通过原生组件input 上的bindinput属性,当键盘输入时触发bindinput属性绑定的方法,实时获取中输入的值,然后将获取到的值放入请求地址中请求数据,再将请求获得的数据放入页面的data数据源中,当请求到的数据不为空时,页面上会显示得到的所有相关数据,如效果一。当按下搜索按钮时会触发input框上bindconfirm属性绑定的事件,此时页面上会显示请求到的数据中的第一条,如效果二。
wxml
<input style='width:500rpx' bindconfirm='confirm' confirm-type='search' focus='true' placeholder="搜索目的地/景点/攻略" bindinput='search'></input> 复制代码
js
// pages/search/search.js const app = getApp() Page({ /** * 页面的初始数据 */ data: { navbarData: { showCapsule: 1, //是否显示左上角图标 1表示显示 0表示不显示 title: '马蜂窝旅游', //导航栏 中间的标题 backgroundColor: '#ffffff', //'#354a98' city: '', opacity: 1, showMain: 0 }, height: app.globalData.height * 2 + 20, result: [], searchparams: '', show: true, searchHistory: [], showResult: false, showconfirm: false, placedata: [] }, <!--清空历史纪录--> clear() { this.setData({ searchHistory: [] }) wx.removeStorageSync('searchHistory') }, <!--当点击键盘上搜索按钮触发的事件--> confirm(e) { if (e.detail.value != '') { let searchHistory = wx.getStorageSync('searchHistory') || [] if (searchHistory.filter(a => a === e.detail.value).length === 0) { searchHistory.push(e.detail.value) wx.setStorageSync('searchHistory', searchHistory) } if (this.data.result.length > 0) { let currentCity = this.data.result[0].name; this.getCityDataByName(currentCity); } this.setData({ show: false, showResult: false, showconfirm: true }) } }, <!--跳到主页面--> gotomain(e) { wx.setStorageSync('currentCity', e.currentTarget.dataset.name) wx.switchTab({ url: '/pages/main/index', }) }, <!--点击历史纪录触发的事件,效果和confirm方法基本相同,不同的是confirm是从页面data中获取数据,而dosearch是从接口中获取数据--> gosearch(e) { let that = this wx.request({ url: `https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/search?name=${e.currentTarget.dataset.name}`, success: (res) => { if (res.data.data.length > 0) { that.getCityDataByName(res.data.data[0].name) } else { this.setData({ show: false, showResult: false, showconfirm: true }) } } }) }, // 通过城市名字 获取城市数据 getCityDataByName(cityname) { let that = this wx.request({ url: 'https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/china', success: (res) => { let placedata = []; placedata.push(...res.data.data.china.filter(e => e.chName === cityname)) that.setData({ placedata, show: false, showResult: false, showconfirm: true }) } }) }, <!--当键盘输入时触发的事件--> search(e) { let that = this wx.request({ url: `https://www.easy-mock.com/mock/5ca457f04767c3737055c868/example/mafengwo/search?name=${e.detail.value}`, success: (res) => { if (res.data.data.length > 0) { that.changecolor(res.data.data, e.detail.value) } else { that.setData({ result: [], searchparams: '', showResult: false }) } } }) }, <!--改变名字颜色--> changecolor(result, searchparams) { for (let j = 0; j < result.length; j++) { let i = result[j].name.search(searchparams); let left = result[j].name.slice(0, i), mid = result[j].name.slice(i, i + searchparams.length), right = result[j].name.slice(i + searchparams.length); result[j].left = left; result[j].mid = mid; result[j].right = right; } this.setData({ result, searchparams, show: false, showResult: true, showconfirm: false }) }, _navback() { wx.navigateBack({ delta: 1 }) }, /** * 生命周期函数--监听页面加载 */ onLoad: function() { <!--获取缓存中的搜索历史并放入数据源--> let searchHistory = wx.getStorageSync('searchHistory') || [] this.setData({ searchHistory }) } 复制代码
这个API接口是我用Easy Mock写的
Easy Mock 代码
{ "data": function({ _req }) { let i = 0, <!--数据源_data由于篇幅原因就放了一小段数据--> _data = [ { name: '亚洲', type: '目的地' }, { name: '欧洲', type: '目的地' }, { name: '大洋洲', type: '目的地' }, { name: '非洲', type: '目的地' }, { name: '北美洲', type: '目的地' }, { name: '南美洲', type: '目的地' }, { name: '南极洲', type: '目的地' } ], <!--_req是easymock封装的对象,_req.query(将查询参数字符串进行解析并以对象的形式返回,如果没有查询参数字字符串则返回一个空对象);--> name = _req.query.name; if (name != '') { <!--当输入的值不为空时--> let result = []; let data = [] for (let j = 0; j < result.length; j++) { <!--eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。这里主要是为了给正则表达式动态传参--> if (eval('/' + name + '/').test(result[j].name)) { data.push(result[j]) } <!--当查询到8个匹配项时跳出循环--> if (data.length > 8) break; } return data } else { <!--当输入的值为空时直接返回空数组--> return [] } } } 复制代码
热门城市动画
因为动画只有6个元素,所以就没有必要写成数组遍历创建了,直接写6个盒子,给他们的样式初始化,让他们到自己的初始位置去。微信小程序提供了创建动画实例的API wx.createAnimation
wxml
<view class='video a' animation="{{animation1}}" data-index='0' bindtap="_play"> <view class='context'> <text>{{placeData.vlog[0].title}}</text> </view> <view class='vdoIcon'> <image src='/images/play.png'></image> </view> </view> <view class='video b' animation="{{animation2}}" data-index='1' bindtap="_play"> <view class='context'> <text>{{placeData.vlog[1].title}}</text> </view> <view class='vdoIcon'> <image src='/images/play.png'></image> </view> </view> <view class='video c' animation="{{animation3}}" data-index='2' bindtap="_play"> <view class='context'> <text>{{placeData.vlog[2].title}}</text> </view> <view class='vdoIcon'> <image src='/images/play.png'></image> </view> </view> <view class='video d' animation="{{animation4}}" data-index='3' bindtap="_play"> <view class='context'> <text>{{placeData.vlog[3].title}}</text> </view> <view class='vdoIcon'> <image src='/images/play.png'></image> </view> </view> <view class='video e' animation="{{animation5}}" data-index='4' bindtap="_play"> <view class='context'> <text>{{placeData.vlog[4].title}}</text> </view> <view class='vdoIcon'> <image src='/images/play.png'></image> </view> </view> <view class='video f' animation="{{animation6}}" data-index='5' bindtap="_play"> <view class='context'> <text>{{placeData.vlog[5].title}}</text> </view> <view class='vdoIcon'> <image src='/images/play.png'></image> </view> </view> 复制代码
wxss
.a{ opacity: 0.9; } .b{ transform: translate(170rpx,-110rpx) scale(0.8); opacity: 0.8; } .c{ transform: translate(210rpx,-250rpx) scale(0.7); opacity: 0.7; } .d{ transform: translate(10rpx,-350rpx) scale(0.6); opacity: 0.6; } .e{ transform: translate(-250rpx,-290rpx) scale(0.8); opacity: 0.5; } .f{ transform: translate(-300rpx,-130rpx) scale(0.9); opacity: 0.8; } 复制代码
js
// 动画的运行路线 translate: function(i) { // 获取屏幕宽度来实现自适应 let windowwidth = this.data.windowWidth; //动画的运行状态status[x轴偏移量,y轴偏移量,scale缩放倍数,opacity透明度],也是动画的运行路线 let status = [ [170, -110, 0.8, 0.7], [210, -250, 0.7, 0.6], [10, -350, 0.6, 0.5], [-250, -300, 0.8, 0.7], [-300, -130, 0.9, 0.8], [0, 0, 1, 0.9] ]; let x = 0, y = 0, scale = 0, opacity = 0; for (let j = 0; j < 6; j++) { let animationName = 'animation' + (j + 1); x = status[(i + j) % 6][0] / 750 * windowwidth; y = status[(i + j) % 6][1] / 750 * windowwidth; scale = status[(i + j) % 6][2]; opacity = status[(i + j) % 6][3]; this.animation.translate(x, y).scale(scale).opacity(opacity).step() this.setData({ [animationName]: this.animation.export()//导出动画数据传递给组件的 animation 属性 }) } }, hotCityAnimation() { let i = 0; <!--创建动画实例--> this.animation = wx.createAnimation({ duration: 2000, timingFunction: 'ease', }) let that = this let anicontrol = this.data.anicontrol anicontrol = setInterval(function() { that.translate(i) if (i == 5) { i = -1; } i++; }, 3000) this.setData({ anicontrol }) } 复制代码
这里要注意的是,因为这是写在tabbar页面的动画,而且用了setinterval定时器,会按照指定的周期(以毫秒计)来执行注册的回调函数,意思就是即使你跳转到别的页面,动画依然在运行,当你回到主页时,动画就会运行出错,出现鬼畜,所以要在主页的onHide周期函数,监听页面隐藏时就把定时器给清除了,并且把动画实例也清除。
onHide: function() { let anicontrol = this.data.anicontrol; clearInterval(anicontrol) this.setData({ animation1: '', animation2: '', animation3: '', animation4: '', animation5: '', animation6: '' }) } 复制代码
关于css
写这个小程序我没有用到任何UI框架,这有坏处,也有好处,坏处就是代码进度贼慢,好处就是自己增加了很多对css的理解。有想用UI框架的可以使用 WeUI 。链接里有详细的使用方法。
结语
因为时间和精力的缘故,小程序只写了几个页面和小部分功能,在写项目的过程中也发现了自己的很多不足,因此吃到了不少苦头,但是也学到了不少,可以说痛并快乐着。希望这篇文章能够对打算写小程序的你有一点帮助。 GitHub源码 在这里,需要自取。
以上所述就是小编给大家介绍的《马蜂窝旅游小程序》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Mastering JavaServer Faces
Bill Dudney、Jonathan Lehr、Bill Willis、LeRoy Mattingly / Wiley / 2004-6-7 / USD 40.00
Harness the power of JavaServer Faces to create your own server-side user interfaces for the Web This innovative book arms you with the tools to utilize JavaServer Faces (JSF), a new standard that wi......一起来看看 《Mastering JavaServer Faces》 这本书的介绍吧!