此次使用react&redux,來模擬了一個購票app,須要關注的是本次所有數據均爲mock實現,不涉及後臺。同時其中不會涉及react與redux的語法,只關注到一些模擬原生效果的實現理念。沒有接觸過react的童鞋們能夠關注下阮一峯老師的react入門教程,至於redux,redux中文文檔上面也有着詳細的說明。不過做者對redux也很感興趣,打算學習一波源碼後(若是整個明白了),可能也會出一個分享,屆時歡迎前來交流~ #github地址,捂臉求starjavascript
本應用所有運行在開發模式下,開啓了devserver,沒有進行過生產環境測試,若是出現問題你們能夠留言~css
git clone https://github.com/Aaaaaaaty/react_movie
cd react_movie
cnpm i || npm i
將./data及./src/images 文件 拷貝進dist //項目依賴的圖片及假數據
npm start複製代碼
本次分享的重點是一個基於react的選座組件demo。做者在開發這個組件的時候有觀察過微信和支付寶內嵌的影院選座功能。可是無奈看不到代碼,一切純平臆想,說錯勿噴。我的感受微信裏面外包的微票兒內的選座模塊裏面的手勢功能爲原生瀏覽器自帶的縮放,那麼控制上會相對粗暴,縮放上面相對沒有支付寶精細。而支付寶上面不只縮放手感好同時包含了左上方小窗預覽功能,可謂用戶體驗良好(我不是阿里腦殘粉hhhhh,雖然事實如此?),因此做者並無感受出來這個是混合開發的組件仍是原生的仍是什麼的。。。好了bb了半天,如今輪到做者本身來實現一個了。html
很惋惜chrome的模擬器下沒法演示手勢的操做。其實這裏面實現了縮放功能,以及在選座界面放大的時候左側上方的預覽圖中的紅色標示線則會相應的縮小來指出你選中的範圍在整個影院中的位置。此次做者使用了react來書寫這個組件,全部的移動縮放所有經過js計算,在真機測試中頁面會有些許卡頓。不過做者相信若是進行防抖和節流的優化,在手機瀏覽器中的體驗應該能夠更優秀一些。java
// ./dist/data/filmSeat.json
{
"seatId":"0000002-1-1",
"rowId": 1, //行index
"columnId": 1, //列index
"xAxis":3, //行絕對定位
"yAxis":1, //列絕對定位
...
"isSold":false //是否賣出(用於渲染座位顏色)
}複製代碼
在這裏須要注意的是:行和列的index值與其絕對定位的區別。咱們在電影院中座位擺放的地理位置是千奇百怪的,可是索引序號必定是從1到X。從而就有了如上的四個屬性。在渲染座位佈局的時候必定是採用xAxis & yAxis
才能達到展現影廳座位排布的效果。若是還有點懵請看上圖的演示中的座位的排布。react
在這裏咱們先假設要渲染一個佔設備視口80%寬的區域來擺放咱們的座椅。那麼由此就會有一個問題就是咱們不肯定座椅的數量。故座椅的寬是不能定死的(方便起見,讓座椅爲正方形,寬高相等),即寬度應爲 視口寬*80% / 座椅數量。git
固然若是座椅太少那麼就會致使寬太大這種狀況這些極端條件若是有興趣能夠後期再進行判斷github
// ./src/Components/FilmSeat/FilmSeat.js
let list = seatList.map((item, index) => {
let style = {
position: 'absolute',
left: `${seatWidth * item.xAxis + seatWidth / 2 }rem`,
top: `${seatWidth * item.yAxis}rem`, // 根據數據中的絕對定位來動態渲染座位位置
width: `${seatWidth}rem`
}
return (
<img key={ 'seatId' + index }
style={ style }
src={ `.\/images\/${isSoldUrl[index]}.png` }
onTouchTap={ this.changeSeat.bind(this, isSoldUrl, index, item) }
className={ styles.seatItem }></img> // 每一個座位都是一張小圖
)
})複製代碼
// ./src/Components/FilmSeat/FilmSeat.js
<div ...
onTouchStart={ this.onTouchStart.bind(this) }
onTouchMove={ this.onTouchMove.bind(this) }
onTouchEnd={ this.onTouchEnd.bind(this) }>複製代碼
對於手勢操做,採用了瀏覽器的三個原生觸摸事件。下面主要說明如何使用react實現一個原生的拖拽效果:chrome
// ./src/Components/FilmSeat/FilmSeat.js
onTouchStart(e) { //三個事件均會傳入event事件
e.preventDefault()
let { left, top... } = this.state
...
if(e.touches.length === 1) { //判斷是否爲一個手指觸摸
let startX = e.touches[0].clientX //獲得起始橫座標
let startY = e.touches[0].clientY //獲得起始縱座標
state = {
startX: startX,
startY: startY,
lastDisX: left, //記錄上一次橫軸偏移量
lastDisY: top, //記錄上一次縱軸偏移量
...
}
}
...
this.setState(state)
}
onTouchMove(e) {
e.preventDefault()
let { startX, startY ... } = this.state
if(e.touches.length === 1) {
let moveX = e.touches[0].clientX //記錄當前的位置
let moveY = e.touches[0].clientY
let disX = moveX - startX + lastDisX //記錄如今手指相對屏幕左側距離
let disY = moveY - startY + lastDisY
...
this.setState({
moveX: moveX,
moveY: moveY,
left: disX,
top: disY,
})
} else if(e.touches.length === 2) {
...
}
}
onTouchEnd(e) {
e.preventDefault()
...
//主要作一些拖拽完成以後的判斷,重置初始值等等
}複製代碼
總結來講核心思路是,e.touches[0].clientX/Y
能夠提供手指在屏幕中的絕對距離,咱們滑動中能夠記錄到滑動了的相對距離。那麼在下次滑動前就須要記錄下上一次的相對距離,下次滑動時就要加上上次的距離。否則每次從新拖拽就會從0,0點從新開始。npm
經過效果圖咱們能夠知道,在組件中同時須要渲染座位的選取,下方彈出/關閉座位信息等效果。雖然效果多樣可是基本能夠看爲兩個狀態即座位是否選中,這就使用到了redux來做爲狀態管理。經過redux來抽象出公共狀態,讓不一樣的效果渲染都基於同一個狀態,從而達到效果聯動。json
// ./src/Container/FilmChooseSeat.js
changeSeatConf(item, isSoldUrl, type) {
const { changeFilmBuySeatList } = this.props // 拿到store中傳出來的方法
let data = {
item: item, //座位信息
isSoldUrl: isSoldUrl, //全部座位顏色列表
type: type
}
changeFilmBuySeatList(data)
}
render() {
let { filmSeatList, filmBuyList, location } = this.props
...
return (
<div>
<FilmSeatTitle location={ location }/>
<FilmSeat filmSeatList={ filmSeatList } //選座拖拽區域
filmBuyList={ filmBuyList }
animationTime={ 200 }
changeSeatConf={ this.changeSeatConf.bind(this) }/>
//經過這個函數將組件中事件傳遞到container中,
//由container發起action來進行改變state
<FilmSeatSale filmBuyList= { filmBuyList } //選座信息
filmSeatList={ filmSeatList }
changeSeatConf={ this.changeSeatConf.bind(this) }/>
</div>
)
}
// ./src/Redux/Store/Store.js
export const mapStateToProps =(state)=> {
return {
...
filmSeatList:state.filmChooseSeatReducer.filmSeatList,//電影座位列表
filmBuyList:state.filmChooseSeatReducer.filmBuyList,//電影選座列表
}
}
export const mapDispatchToProps=(dispatch)=> {
return {
...
getFilmSeatList:(url,data)=>dispatch(FilmChooseSeatActions.fetchFilmSeatList(url,data)),//獲取電影座位列表
changeFilmBuySeatList:(data)=>dispatch(FilmChooseSeatActions.changeFilmBuySeatList(data))//選中座位購票
}
}複製代碼
發起action後,在reducer中改變維護的filmBuyList
數組狀態,就能夠同時渲染好整個界面的變化。
// ./src/Redux/Reducer/FilmChooseSeatReducer.js
export const filmBuyList = (state = {item:[],isSoldUrl:{},type:''}, action={})=>{
switch(action.type){
case FilmChooseSeatActions.CHANGE_FILM_BUYSEAT:
let _state = Object.assign({}, state)
if(action.text.type === 'add') {
_state.item.push(action.text.item)
} else {
let index = _state.item.indexOf(action.text.item)
_state.item.splice(index, 1)
}
_state.isSoldUrl = action.text.isSoldUrl
_state.type = action.text.type
return _state
default:
return state
}
}複製代碼
當完成了大圖的渲染以及選座狀態切換的工做以後,只須要複製一份大圖的渲染的那段jsx修改css樣式就能夠完成一個預覽小圖。在這期間你不須要作任何事就能夠看到小圖上面一樣會存在選座狀態的切換,這就是狀態管理的好處。只要你的界面效果和狀態進行了綁定,那麼在以後的工做中你就不須要再去關注效果而只須要關注狀態是否正確便可。在這其中惟一有一點問題的地方是預覽圖中紅色提示框的縮放和大圖的縮放是成反比的。大圖放大預覽圖中的紅色框應該縮小,同時大圖可拖拽的範圍應該和紅框的移動範圍有一個比例係數。在此次的實現中做者用了scaleNum
這個狀態來控制其縮放的係數,有興趣的童鞋能夠本身嘗試一下如何計算一個正確的係數來保證大圖和預覽圖縮放後紅框移動距離和大圖拖拽範圍的匹配。