hi~ 大家好,我叫内孤,一名web前端开发者/:B-,在小程序实现在线选座实战(中)里我们搭建完了layout,下面我们来实现最核心的选座。
在实现选座组件前,我们这里先介绍一下,我们需要的座位表数据结果
[
{
"id": 1,
"price": 4,
"x": 1,
"y": 1
}
]
其中x、y代表这个座位在整个座位表中的横轴和纵轴坐标,下面我们就针对这个数据结果展开实现这个选座组件
1. 首先创建Seat.js文件,并且初始化基本架子:
var Seat = (function(factory) {
return factory.call();
}(function() {
var __CORE__ = {
init: function() {
},
render: function() {
},
setData: function() {
}
};
return __CORE__;
}));
下面创建并且在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>'
);
},
这里还需要动态的计算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');
// 获取座位容器主要的区域
var $seatViewContainer = this.$node.querySelector('.seat-container');
this.layer = $seatViewContainer;
// 初始化设置容器的大小
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,然后根据容器的大小算出每一个座位占用的大小。绝对定位每一个座位,一个座位的left:“座位大小座位的x+偏移量”,top:“座位大小座位的y”,这样遍历整个座位列表我们就可以得到整个座位图:
接下去实现,拖动座位图和放大缩小功能:
_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;
// 执行移动
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));
}
这里监听容器的touchstart 、touchmove 、touchend
判断e.touches.length长度来判断指数,进行缩放或者移动的处理。
下面写监听点击了座位的事件,并抛出外部数据
_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') {
// 如果状态为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);
}
以上基本已经完成了座位表的功能,不过有一个缺点,不能根据指定缩放位置缩放