hi~ 大家好队伟,我叫內(nèi)孤俭尖,一名web前端開發(fā)者/:B-,在小程序?qū)崿F(xiàn)在線選座實戰(zhàn)(中)里我們搭建完了layout桶良,下面我們來實現(xiàn)最核心的選座座舍。
在實現(xiàn)選座組件前,我們這里先介紹一下陨帆,我們需要的座位表數(shù)據(jù)結(jié)果
[
{
"id": 1,
"price": 4,
"x": 1,
"y": 1
}
]
其中x曲秉、y代表這個座位在整個座位表中的橫軸和縱軸坐標采蚀,下面我們就針對這個數(shù)據(jù)結(jié)果展開實現(xiàn)這個選座組件
1. 首先創(chuàng)建Seat.js文件,并且初始化基本架子:
var Seat = (function(factory) {
return factory.call();
}(function() {
var __CORE__ = {
init: function() {
},
render: function() {
},
setData: function() {
}
};
return __CORE__;
}));
下面創(chuàng)建并且在init初始化模版:
// __CORE__ 里面的方法
init: function(options) {
this.$el = document.querySelector(options.el);
this.data = [];
this.selectedData = [];
this.$el.innerHTML = this._getDefaultTpl();
},
_getDefaultTpl: function() {
return (
'<div class="seatComponent">' +
'<div class="screen">' +
'<span class="title">舞臺</span>' +
'</div>' +
'<div class="seat-container">' +
'</div>' +
'</div>'
);
},
這里還需要動態(tài)的計算seatComponent
和seat-container
的大小
// 添加計算容器的大小
_computedContainerSize: function() {
var seatContainer = this.$node.parentNode.getBoundingClientRect();
return {
width: seatContainer.width,
height: seatContainer.height
};
}
// 修改init
init: function(options) {
this.$el = document.querySelector(options.el);
this.data = [];
this.selectedData = [];
this.$el.innerHTML = this._getDefaultTpl();
this.$node = this.$el.querySelector('.seatComponent');
// 獲取座位容器主要的區(qū)域
var $seatViewContainer = this.$node.querySelector('.seat-container');
this.layer = $seatViewContainer;
// 初始化設(shè)置容器的大小
var boxSize = this._computedContainerSize();
this.$node.style.width = boxSize.width + 'px';
this.$node.style.height = boxSize.height + 'px';
$seatViewContainer.style.width = boxSize.width + 'px';
// 42 是舞臺模塊寫死的height+margin
$seatViewContainer.style.height = boxSize.height - 42 + 'px';
}
在渲染座位前承二,我們先寫一個setData
方法來注入座位信息
setData: function(data) {
this.data = data;
this._renderSeat();
},
// 渲染座位表
_renderSeat: function() {
var me = this;
var data = me.data;
if (!data.length) return;
var seatsList = this._createdSeat(data);
this.layer.innerHTML = seatsList;
},
_createdSeat: function(data) {
var me = this;
var seatsList = '';
var width = this.width - 20; // 減去20為了給整個座位表添加一個padding
var maxSize = this._getWrapperSize(data);
// 計算一個座位的大小
var seatWidth = parseInt(width / maxSize.x);
// 計算整個座位表x軸占滿后榆鼠,剩余的寬度
var overWidth = width - maxSize.x * seatWidth;
// 計算左右可用padding
var offsetLeft = Math.floor(overWidth / 2);
for (var i = 0, len = data.length; i < len; i++) {
var item = data[i];
var _seatLeft = seatWidth * item.x + offsetLeft;
var _seatTop = seatWidth * item.y;
// -2 為了空出座位和座位之間的間隙
var _seatWidth = seatWidth - 2;
var _seatHeight = seatWidth - 2;
var style = 'position: absolute; transform: matrix(1, 0, 0, 1,' + _seatLeft.toFixed(1) + ',' + _seatTop.toFixed(1) + '); width:' + _seatWidth.toFixed(1) + 'px;height:' +
_seatHeight.toFixed(1) + 'px;background-color:' + (item.status === 0 ? '#fff' : '#989898') + ';';
seatsList += '<div class="seat ' + 'seatId-' + item.id + '" data-index="' + i + '" data-type="seat" data-id="' + item.id + '" data-status="' + item.status + '" style="' + style + '"></div>';
}
return seatsList;
},
// 獲取seat中最大的x和y
_getWrapperSize: function(list) {
var maxX = 0;
var maxY = 0;
if (!list) list = [];
for (var i = 0, len = list.length; i < len; i++) {
if (list[i].x > maxX) {
maxX = list[i].x;
}
if (list[i].y > maxY) {
maxY = list[i].y;
}
}
return {
x: maxX,
y: maxY
};
}
通過_getWrapperSize
方法算出最大x和y,然后根據(jù)容器的大小算出每一個座位占用的大小亥鸠。絕對定位每一個座位妆够,一個座位的left:“座位大小座位的x+偏移量”,top:“座位大小座位的y”负蚊,這樣遍歷整個座位列表我們就可以得到整個座位圖:
image.png
接下去實現(xiàn)神妹,拖動座位圖和放大縮小功能:
_onTouchLayer: function() {
var me = this,
startX,
startY,
distance = {},
origin,
scale;
me.isMove = false;
me.isCanScale = false;
me.scale = 1;
me.$node.addEventListener('touchstart', function(e) {
e.preventDefault();
me.isMove = false;
if (e.touches.length === 1) {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
} else if (e.touches.length > 1) {
// 開始縮放
me.isCanScale = true;
distance.start = me._getDistance({
x: e.touches[0].clientX,
y: e.touches[0].clientY
}, {
x: e.touches[1].clientX,
y: e.touches[1].clientY
});
}
}, false);
me.$node.addEventListener('touchmove', function(e) {
e.preventDefault();
var moveX, moveY, disX, disY;
if (e.touches.length === 1) {
moveX = e.touches[0].clientX;
moveY = e.touches[0].clientY;
disX = Math.round(moveX - startX);
disY = Math.round(moveY - startY);
if (Math.abs(disX) + Math.abs(disY) > 0) {
me.isMove = true;
}
startX = moveX;
startY = moveY;
// 執(zhí)行移動
me._transformLayer(disX, disY);
} else if (e.touches.length === 2) {
origin = me._getOrigin({
x: e.touches[0].clientX,
y: e.touches[0].clientY
}, {
x: e.touches[1].clientX,
y: e.touches[1].clientY
});
distance.stop = me._getDistance({
x: e.touches[0].clientX,
y: e.touches[0].clientY
}, {
x: e.touches[1].clientX,
y: e.touches[1].clientY
});
scale = Math.ceil(distance.stop / distance.start + me.scale - 1);
if (scale >= 2) {
me.scale = 5;
} else {
me.scale = 1;
}
if (distance.stop - distance.start > 0) {
// 放大
me.scale = scale;
if (scale > 5) me.scale = 5;
} else if (distance.stop - distance.start < 0) {
// 縮小
me.scale -= scale;
if (scale < 5) me.scale = 1;
}
me.isMove = true;
me._scaleLayer(origin, me.scale);
}
}, false);
me.$node.addEventListener('touchend', function(e) {
e.preventDefault();
me.isCanScale = false;
if (me.scale === 1) {
me._resetTransFormLayer();
}
}, false)
},
_scaleLayer: function(origin, scale) {
var x, y;
x = origin.x + (-origin.x) * scale;
y = origin.y + (-origin.y) * scale;
this.layer.style.transform = 'translate3d(' + this.layerLeft + 'px, ' + this.layerTop + 'px, 0) scale(' + scale + ')';
},
_transformLayer: function(disX, disY) {
var me = this;
// 如果正在縮放,則不進行移動
if (me.isCanScale) true;
if (this.layerTop > 100) {
this.layerTop = 100;
}
if (me.scale === 5) {
// doc.querySelector('.sureBtn').innerText = disY
if (this.layerLeft >= 900 && disX > 0) {
disX = 0;
}
if (this.layerLeft <= -900 && disX < 0) {
disX = 0;
}
if (this.layerTop <= -1000 && disY < 0) {
disY = 0;
}
}
this.layerLeft += disX;
this.layerTop += disY;
// 開啟3D加速移動位置
this.layer.style.transform = 'translate3d(' + this.layerLeft + 'px, ' + this.layerTop + 'px, 0) scale(' + me.scale + ')';
},
_resetTransFormLayer: function() {
this.layer.style.transform = 'translate3d(' + this.oldLayerLeft + 'px, ' + this.oldLayerTop + 'px, 0) scale(' + this.scale + ')';
this.layerLeft = this.oldLayerLeft;
this.layerTop = this.oldLayerTop;
},
_getOrigin: function(first, second) {
return {
x: (first.x + second.x) / 2,
y: (first.y + second.y) / 2
};
},
_getDistance: function(start, stop) {
return Math.sqrt(Math.pow((stop.x - start.x), 2) + Math.pow((stop.y - start.y), 2));
}
這里監(jiān)聽容器的touchstart 家妆、touchmove 鸵荠、touchend
判斷e.touches.length長度來判斷指數(shù),進行縮放或者移動的處理揩徊。
下面寫監(jiān)聽點擊了座位的事件腰鬼,并拋出外部數(shù)據(jù)
_onTouchSeat: function () {
var me = this;
this.layer.addEventListener('touchend', function (e) {
e.preventDefault();
if (me.isMove) return;
var target = e.target;
var type = target.getAttribute('data-type');
var id = target.getAttribute('data-id');
var status = target.getAttribute('data-status');
var index = target.getAttribute('data-index');
var data = me.data;
if (type && type === 'seat') {
// 如果狀態(tài)為0, 則可以進行選擇
if (status == 0) {
// 檢測當前是取消還是選中
if (target.className.indexOf('active') > -1) {
// 取消
target.className = target.className.replace('active', '');
target.style.backgroundColor = '#fff';
me._removeSelectedSeat(id, data[index], index);
} else {
// 選中
target.className = target.className + ' active';
target.style.backgroundColor = 'inherit';
me._addSelectedSeat(id, data[index], index);
}
}
}
});
},
_addSelectedSeat: function(id, item, index) {
item.index = index;
this.selectedData.push(item);
this._onChange();
},
_removeSelectedSeat: function(id, item) {
var selectedData = this.selectedData;
var index = 0;
var i = 0;
var len = selectedData.length;
for (i; i < len; i++) {
if (selectedData[i] === item.id) {
index = i;
break;
}
}
selectedData.splice(index, 1);
this._onChange();
},
_onChange: function() {
var selectedData = this.selectedData;
this.onChange(selectedData);
}
以上基本已經(jīng)完成了座位表的功能,不過有一個缺點塑荒,不能根據(jù)指定縮放位置縮放