618 將至,學了這麼久小程序,也該本身動手實踐一下。最近實現了一個京東到家小程序,我將其中比較有意思,並且經常使用到的功能作一個展現,你們能夠參考參考,完整項目在這裏 github.daojia.jdcss
本文的實現html
若是是 DOM 編程,天然是獲取點擊的位置,用 createElement 建立一個元素,定時器給它一個動態的座標,讓它運動起來,但這並非 DOM ,而是小程序,使用的是數據綁定。github
那這就更好了,只要數據改變,狀態就會相應的改變,那咱們趕快給它實現座標的動態輸入吧。若是你真的就這樣開始作了,那你就會陷入和我以前同樣的困境,小球運動的初始位置不是固定的,你想要讓它在任何位置都能圓潤地滾到購物車,就須要用一個算法,實現這條拋物線。算法
有聰明的小夥伴可能就想到了貝塞爾曲線,是的這樣確實可行,但對與我這樣的數學「天才」來講,正餘弦什麼的仍是不了不了,打擾了,告辭。編程
我就想有什麼更簡單的方式,可以動態的添加拋物線動畫呢,這時我上百度Google到了一個 api createAnimation
,就決定是你了。小程序
這是 wx.createAnimation 的官方文檔,簡單來講就是,它會建立一個動畫實例,這個實例能夠描述你想要的動畫,並經過頁面的 animation
屬性綁定動畫。api
那就開始了數組
<view animation="{{animationY}}" style="position:fixed;top:{{ballY}}px;" hidden="{{!showBall}}">
<view class="ball" animation="{{animationX}}" style="position:fixed;left:{{ballX}}px;"></view>
</view>
複製代碼
外層 view 實現 x軸運動,內層 view 實現 縱軸y運動。 ballX,ballY是它的(座標)位置,就是以屏幕左上爲原點的座標軸。app
runBall(e) {
// 從全局獲取屏幕高度和寬度
let bottomX = this.data.screenWidth,
bottomY = this.data.screenHeight
// x, y表示手指點擊橫縱座標, 即小球的起始座標
let ballX = e.detail.x,
ballY = e.detail.y
this.setData({
ballX: ballX-10,
ballY,
showBall: false,
jump: false
})
//實例化動畫
this.animationX = wx.createAnimation({
duration: 1000,
timingFunction: 'linear',
})
this.animationY = wx.createAnimation({
duration: 1000,
timingFunction: 'cubic-bezier(.93,-0.11,.85,.74)',
})
// 第一段動畫,向上運動一點
this.setDelayTime(10).then(() => {
this.animationX.translateX(-100).step()
this.animationY.translateY(-10).step()
this.setData({
animationX: this.animationX.export(),
animationY: this.animationY.export()
})
return this.setDelayTime(200);
}).then(() => {
// 第二段動畫,落入購物車
this.setData({
showBall: true
})
// 平移距離是根據個人元素位置,其餘須要自行調整
this.animationX.translateX(-ballX-ballY*0.5).step()
this.animationY.translateY(bottomY).step()
this.setData({
animationX: this.animationX.export(),
animationY: this.animationY.export()
})
return this.setDelayTime(1000);
}).then(() => {
// 重置動畫,回到原點
this.animationX.translateX(0).step()
this.animationY.translateY(0).step()
this.setData({
showBall: false,
animationX: this.animationX.export(),
animationY: this.animationY.export()
})
})
},
// 同步操做,傳入定時器時間,
setDelayTime(second) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
}, second)
})
},
複製代碼
.ball {
width: 40rpx;
height: 40rpx;
background-color: #4DCC57;
border-radius: 50%;
z-index: 999;
}
複製代碼
這段js作的事情:
關於 wx.createAnimation
api 的使用,實例.運動方式.step()
,step
分隔動畫,export()
導出動畫,timingFunction
規定運動速度效果,詳細可見文檔。
Promise 是異步編程的一種解決方案,比傳統的解決方案–回調函數和事件--更合理和更強大。這裏只須要知道,可讓這幾段動畫分步執行的就行。詳細見廖雪峯Promise
另外,有一個小彩蛋就是,顯示數量的小紅點,在每次小球落入購物車的時候跳一下,以提醒用戶。是這樣實現的:算了,見下一段
這個小紅點是用 weui
的 badge
效果,關於badge
詳情見官方文檔:weui.wxml
<view class="weui-badge {{jump?'badgeJump':''}}" style="position: absolute; display: {{shopCar.length==0?'none':''}}">{{shopCar.length}}</view>
複製代碼
動態添加類名,jump 決定是否使用 badgeJump 類。
this.runBall(e)
this.setDelayTime(1000).then(() => {
this.hasCarList()
this.getTotalPrice()
this.setData({
jump: true
})
})
複製代碼
.badgeJump {
animation: jump 500ms;
}
@keyframes jump {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-50rpx);
}
100% {
transform: translateY(0);
}
}
複製代碼
一樣是在調用 runBall 方法的事件中,先調用runBall方法,在小球下落時間結束以後再將值傳入,這樣頁面的效果跟着小球的下落在改變,最後小紅點調皮地跳動一下,提示用戶有新商品加入購物車啦。
想要實現的是點擊一下購物車圖標,從下而上彈出一個菜單,顯示已添加進購物車的物品。能夠用 weui actionsheet 實現,沒錯又是 weui。可是我想要的是一個滾動區域而不是固定的,因此仍是本身寫一個相似原生actionsheet 的吧,還能夠練手。
首先要肯定的是,整個頁面就是一屏大小,頁面不能撐出滾動條,但內部能夠有滾動區域。遮罩就是整個屏幕的大小,購物車區域能夠自行設定一個高度,給它一個固定定位,初始位置是在整個屏幕的下面,並不顯示在可視區,當用戶點擊購物車圖標時,從下方彈出來,單擊遮罩區域,縮回去回去並隱藏。來看代碼是怎麼實現的:
<!-- 遮罩區域 類名控制隱藏與否 -->
<view class="{{clicked?'weui-mask':'weui-mask-on'}}" catchtap="hideMask"></view>
<!-- 購物車區域 -->
<view class="weui-actionSheet {{clicked?'weui-actionsheet_toggle':''}}">
<view class="cart-header">
<icon class="total-select" type="{{selectAllStatus?'success':'circle'}}" color="#4DCC57" bindtap="selectAll" />
<text>{{selectAllStatus?'全選':'全不選'}}</text>
<text class="cart-total-num"> (已選{{shopCar.length}}件)</text>
<text class="clear_cart" catchtap="clearCart">清空購物車</text>
</view>
<!-- 加入購物車的商品列表 -->
<scroll-view scroll-y style="width: 100%; height: 700rpx;">
</scroll-view>
</view>
複製代碼
<!-- 底部購物車圖標區域 -->
<view class="cart" style="margin-top: 0;">
<view class="shop_ft" style="z-index: -999;">
<view class="weui-cell shop_car">
<view class="weui-cell__hd {{shopCar.length==0?'car-icon_empty':'car-icon_fill'}}" catchtap="{{shopCar.length==0?'':'showCart'}}">
<view class="weui-badge {{jump?'badgeJump':''}}" style="position: absolute; display: {{shopCar.length==0?'none':''}}">{{shopCar.length}}</view>
</view>
<view class="weui-cell__bd car_status" style="{{shopCar.length==0?'':'color: red;'}}">
<text>{{shopCar.length==0?'購物車是空的':car_status}}</text>
</view>
</view>
<view class="weui-cell__ft pay" style="{{shopCar.length==0?'':'background: #4DCC57'}}" catchtap="submitOrder">
<text>去結算</text>
</view>
</view>
</view>
複製代碼
/* 遮罩層 */
.weui-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, .6);
}
.weui-mask-on {
display: block;
}
/* 購物車 actionsheet */
.weui-actionSheet {
background-color: #fff;
position: fixed;
bottom: 0;
width: 100%;
max-height: 800rpx;
margin-bottom: 100rpx;
z-index: 99;
/* 事先隱藏才下方 */
transform: translateY(100%);
transition: transform .3s;
font-size: 32rpx;
backface-visibility: hidden;
/*定義當元素不面向屏幕時是否可見*/
}
/* 回到原位顯示出來 */
.weui-actionsheet_toggle {
transform: translate(0, 0);
transition: transform .3s;
}
複製代碼
hideMask() {
this.setData({
clicked: false
})
},
// 點擊購物車的觸發事件
showCart() {
const shopCar = wx.getStorageSync('myCart')
shopCar.forEach(item => {
if (item.id == this.data.shop_id) {
myCart = item.list
}
})
this.setData({
// 購物車和遮罩的顯隱
clicked: !this.data.clicked,
shopCar: myCart
})
this.getTotalPrice()
},
複製代碼
這個功能的難點應該是樣式,是整個頁面高度的控制和購物車區域的定位,這裏還有一個scroll-view
高度自適應的方法,能夠試一試,
flex 佈局 、scroll-view的容器設置 flex: auto; overflow: auto;
wx.getLocation(OBJECT)
,nice,那不就OK了嗎?大家哪,仍是 naive,too young,too simple。仔細看,該方法只是返回的位置座標等信息,並未返回地理位置名稱。
騰訊位置服務 提供的接口 SDK 能夠解決問題。怎麼弄官方文檔已經寫的很清楚了,接下來就看看我是怎麼應用的
首先,按照文檔申請祕鑰,並下載 SDK,下載完成後,將文件解壓到 utils 文件夾,這裏有兩個文件,壓縮和不壓縮的,建議壓縮的。
而後,在須要獲取位置的文件中引入
const QQMapWX = require('../../utils/qqmap-wx-jssdk')
const qqmapsdk
複製代碼
接下來就可使用SDK獲取詳細位置了,以下
<view class="location">
<navigator class="par" hover-class="none" url="../location/location?page=1">
<image src="../../assets/images/location.png"/>
<!-- 顯示地址 -->
<text>{{address}}</text>
</navigator>
</view>
複製代碼
index.js
onLoad(options) {
/*判斷是第一次加載仍是從position頁面返回 若是從position頁面返回,會傳遞用戶選擇的地點*/
if (app.globalData.address) {
//設置變量 address 的值
this.setData({
address: app.globalData.address
});
} else {
// 實例化API核心類
qqmapsdk = new QQMapWX({
//此key須要用戶本身申請
key: 'JF2BZ-DDH65-DCUIH-QU3TN-FT4UF-EEFEH'
});
var that = this
// 調用接口
qqmapsdk.reverseGeocoder({
success: function (res) {
that.setData({
address: res.result.address // 詳細地址
})
},
fail: function (res) {
// console.log(res)
},
complete: function (res) {
// console.log(res)
}
});
}
},
複製代碼
個人需求是,在進入程序時,自動獲取位置信息,並顯示在頁面,因此直接放置在 onload
裏,success
的回調函數返回的 res.result.address
就是詳細信息了.
而我這裏另外作了一個判斷,是由於我還提供了一個用戶自主選擇地址的功能,地址顯示優先爲用戶自主選擇的。有相同需求的小夥伴能夠繼續看下去。
<view class="weui-cells weui_cell__nav-chooseLocation" bindtap="chooseLocation" style="display: {{page==1?'':'none'}};">
<view class="weui-cell">
<view class="weui-cell__hd">
<image src="http://static-o2o.360buyimg.com/daojia/new/images/icon/location-eye@2x.png" style="margin-right: 5px;vertical-align: top;width:20px; height: 20px;"></image>
</view>
<view class="weui-cell__bd">點擊定位當前地點</view>
<view class="weui-cell__ft weui-cell__ft_in-access"></view>
</view>
</view>
複製代碼
location.js
//選擇地點
chooseLocation: function () {
wx.chooseLocation({
success: res => {
//選擇地點以後返回到首頁
wx.switchTab({
url: '/pages/index/index'
})
this.setData({
address: res.address
})
// 設置全局變量
app.globalData.address = res.address
},
fail: err => {
console.log(err)
}
})
},
複製代碼
綁定的 catchtab 屬性觸發 chooseLocation
事件,wx.chooseLocation
api 將直接返回當前位置,若是沒有,檢查是否開啓定位。將位置存入全局變量,在返回首頁以後可見,地址已改成用戶選擇的。
這裏的頁面跳轉選擇
switchTab
而不是經常使用的navigateTo
或redirectTo
,是由於小程序爲了性能消耗考慮,不容許打開超過 5 個的頁面,返回主頁tabbar
使用switchTab
是較好的選擇,而經過設置全局變量 address 來傳遞地址信息,也正是在這種狀況下,比較適用的傳遞數據的方法之一。
看起來這個查詢有不少,好比關鍵字啊,熱搜啊,歷史記錄查啊,但實際上用的都是同一個查詢器,先來看看吧
weui
的 searchbar
,簡單實用<view class="weui-search-bar">
<view class="weui-search-bar__form">
<view class="weui-search-bar__box">
<icon class="weui-icon-search_in-box" type="search" size="14"></icon>
<input type="text" class="weui-search-bar__input" placeholder="{{placeholder}}" value="{{inputVal}}" bindinput="inputTyping" />
<view class="weui-icon-clear" wx:if="{{hasItems}}" bindtap="clearInput">
<icon type="clear" size="14"></icon>
</view>
</view>
</view>
<view class="weui-search-bar__btn">
<text class="weui-search-bar__text" catchtap="doSearch">搜索</text>
</view>
</view>
複製代碼
bindinput="inputTyping"
,它能夠監測你的每一次輸入,並經過 e.detail.value
向你反饋出來,咱們能夠以此得到關鍵字。inputTyping: function (e) {
this.setData({
search: {
inputVal: e.detail.value // 因爲個人搜索是使用模板創建的,數據傳遞須要嵌套一下
},
input: e.detail.value,
})
this.showKeyList(e.detail.value)
},
複製代碼
weui-searchbar
自帶清空搜索框,挺方便,接拿來用。clearInput: function () {
this.setData({
search: {
inputVal: ""
},
})
},
複製代碼
split
方法,分割爲字符串數組,在關鍵字列表中查詢每個關鍵字符,使用 indexOf
方法檢索,若是存在這樣一個字符,就返回這個字符的下標,若是沒有將返回 -1
,最後將檢索結果放入數組中,這就是在搜索框下部顯示的關鍵字列表了。showKeyList(keyWords) {
const key = keyWords ? keyWords.split('') : []
const keyListAll = app.globalData.keyList
const keyList = []
keyListAll.forEach(item => {
key.forEach(k => {
let i = item.indexOf(k)
if (i > -1) {
keyList.push(item)
}
})
})
this.setData({
search: {
keyList, // 查詢結果將在頁面顯示
}
})
},
複製代碼
// 關鍵字查詢
search(keyWords, arr) {
const cur = []
const desc = new Set() // 使用 set 能夠去重
const key = keyWords ? keyWords.split('') : []
arr.forEach(ele => {
key.forEach(k => {
let i = ele.title.indexOf(k)
if (i > -1) {
cur.push(ele.id)
desc.add(ele)
}
})
})
this.setData({
cur,
search: {
desc: Array.from(desc)
}
})
},
// 數據查詢
searchItem(keyWords) {
const goods = app.globalData.details,
shops = app.globalData.shopInfo,
temp = [],
list = []
goods.forEach(item => {
item.desc.forEach(ele => {
temp.push(ele)
})
})
// 調用搜索
this.search(keyWords, temp)
shops.forEach(item => {
this.data.cur.forEach(i => {
if (item.goodslist.indexOf(i) > -1) {
list.push(item)
}
})
})
var resList = Array.from(new Set([...list]))
this.setData({
search: {
resList,
desc: this.data.search.desc,
},
})
if (this.data.search.resList.length > 0) {
this.setData({
hasItems: true,
search: {
resList,
desc: this.data.search.desc,
hasItems: true,
},
})
} else {
this.setData({
hasItems: false,
search: {
hasItems: false,
},
})
}
},
複製代碼
add_search(e) {
const index = e.currentTarget.dataset.index
this.setData({
search: {
inputVal: index,
},
input: index,
})
this.searchItem(index)
},
複製代碼
其實查詢功能還能夠精簡不少,只是個人僞數據數組太多,須要聯合查詢,比較麻煩。感受數據處理仍是SQL語句方便多哈,多表查詢,模糊查詢。。。
但願對你們有幫助