目录
- 购物车效果
- 分析数据
- 单件商品的数据
- 整个界面的数据
- 分析界面
- 分析事件
购物车效果
先准备好原始数据和素材
在下面数据的基础上,编写index.js
分析数据
- 编写程序要从数据入手,从数据到界面最后到事件
- 在分析数据的过程中,要分析数据是通过属性出现还是通过方法出现
单件商品的数据
我们观察
data.js
中的数据:商品数组goods
- pic:图片
- title:标题
- desc:描述
- sellNumber:月售
- favorRate:好评率
- price:价格
为了避免改变原始数据。我们创建一个单件商品的数据的类(class)
- 代码如下:
// 单件商品的数据
class UIGoods{
constructor(g){
this.data = g;
this.choose = 0; // 每件商品被选中的数量
}
// 获取商品的总价
getTotalPrice(){
return this.data.price * this.choose;
}
// 是否选中此商品
isChoose(){
return this.choose > 0;
}
// 商品选择数量+1
increase(){
this.choose++;
}
// 商品选择数量-1
decrease(){
this.choose--;
}
}
- 对这个类进行测试
var uig = new UIGoods(goods[0]);
- 测试成功进行下一步
整个界面的数据
分析整个页面需要用到的数据,创建UIData类,并进行测试(大家可以自行在控制台进行测试)
- 代码如下
// 整个界面的数据
class UIData{
constructor(){
var uiGoods = [];
for(let i=0; i<goods.length; i++){
let uig = new UIGoods(goods[i]);
uiGoods.push(uig);
}
this.uiGoods = uiGoods;
this.deliveryThreshold = 30; //起送费
this.deliverPrice = 5; //配送费
}
// 获取总价
getTotalPrice(){
var sum = 0;
for(let i=0; i<this.uiGoods.length; i++){
let g = this.uiGoods[i];
sum += g.getTotalPrice();
}
return sum;
}
// 增加某件商品的数量
increase(index){
this.uiGoods[index].increase();
}
// 减少某件商品的数量
decrease(index){
this.uiGoods[index].decrease();
}
// 得到总共的选中数量
getTotalChooseNumber(){
var sum = 0;
for(let i=0; i<this.uiGoods.length; i++){
sum += this.uiGoods[i].choose;
}
return sum;
}
// 判断购物车中有没有商品
hasGoodsInCar(){
return this.getTotalChooseNumber() > 0;
}
// 判断是否跨过了配送标准
isCrossDeliveryThreshold(){
return this.getTotalPrice() >= this.deliveryThreshold;
}
// 判断该商品是否被选中
isChoose(index){
return this.uiGoods[index].isChoose();
}
}
分析界面
在分析完数据逻辑之后,我们来分析界面之间的逻辑关系
创建一个UI类
- 代码如下:
// 整个界面
class UI{
constructor(){
this.uiData = new UIData();
this.doms = {
goodsContainer:document.querySelector('.goods-list'),
deliverPrice:document.querySelector('.footer-car-tip'),
footerPay:document.querySelector('.footer-pay'),
footerPayInnerSpan:document.querySelector('.footer-pay span'),
totalPrice:document.querySelector('.footer-car-total'),
car:document.querySelector('.footer-car'),
badge:document.querySelector('.footer-car-badge')
}
var carRect = this.doms.car.getBoundingClientRect();
var jumpTarget = {
x: carRect.left + carRect.width / 2,
y: carRect.top + carRect.height / 5,
};
this.jumpTarget = jumpTarget;
this.createHTML();
this.updateFooter();
this.listenEvent();
}
// 监听各种事件
listenEvent(){
this.doms.car.addEventListener('animationend', function(){
this.classList.remove('animate');
});
}
// 根据商品数据,创建商品列表
createHTML(){
// 1. 生成html字符串(parse html) 执行效率低,开发效率高
// 2. 一个一个创建元素 执行效率高,开发效率低
// 这里我们采用第一种方式
var html = '';
for(let i=0; i<this.uiData.uiGoods.length; i++){
var g = this.uiData.uiGoods[i];
html += `<div class="goods-item">
<img src="${g.data.pic}" alt="" class="goods-pic" />
<div class="goods-info">
<h2 class="goods-title">${g.data.title}</h2>
<p class="goods-desc">
${g.data.desc}
</p>
<p class="goods-sell">
<span>月售 ${g.data.sellNumber}</span>
<span>好评率${g.data.favorRate}</span>
</p>
<div class="goods-confirm">
<p class="goods-price">
<span class="goods-price-unit">¥</span>
<span>${g.data.price}</span>
</p>
<div class="goods-btns">
<i index="${i}" class="iconfont i-jianhao"></i>
<span>${g.choose}</span>
<i index="${i}" class="iconfont i-jiajianzujianjiahao"></i>
</div>
</div>
</div>
</div>`;
}
this.doms.goodsContainer.innerHTML = html;
}
// 界面的增加减少
increase(index){
this.uiData.increase(index);
this.updateGoodsItem(index);
this.updateFooter();
this.jump(index);
}
decrease(index){
this.uiData.decrease(index);
this.updateGoodsItem(index);
this.updateFooter();
}
// 更新某个商品元素的显示状态
updateGoodsItem(index){
var goodsDom = this.doms.goodsContainer.children[index];
if(this.uiData.isChoose(index)){
goodsDom.classList.add('active');
}else{
goodsDom.classList.remove('active');
}
var span = goodsDom.querySelector('.goods-btns span');
span.textContent = this.uiData.uiGoods[index].choose;
}
// 更新页脚
updateFooter(){
var total = this.uiData.getTotalPrice();
this.doms.deliverPrice.textContent = `配送费¥${this.uiData.deliverPrice}`;
if(this.uiData.isCrossDeliveryThreshold()){
// 到达起送点
this.doms.footerPay.classList.add('active');
}else{
this.doms.footerPay.classList.remove('active');
// 更新还差多少钱
var dis = this.uiData.deliveryThreshold - total;
dis = Math.round(dis);
this.doms.footerPayInnerSpan.textContent = `还差¥${dis}元起送`;
}
// 总价元素,设置总价
this.doms.totalPrice.textContent = total.toFixed(2);
// 设置购物车的样式状态
if(this.uiData.hasGoodsInCar()){
this.doms.car.classList.add('active');
}else{
this.doms.car.classList.remove('active');
}
// 设置购物车中的数量
this.doms.badge.textContent = this.uiData.getTotalChooseNumber();
}
// 购物车动画
carAnimate(){
this.doms.car.classList.add('animate');
}
// 抛物线跳跃的元素
jump(index){
// 找到对应商品的加号
var btnAdd = this.doms.goodsContainer.children[index].querySelector('.i-jiajianzujianjiahao');
var rect = btnAdd.getBoundingClientRect();
var start = {
x:rect.left,
y:rect.top
};
// 跳
var div = document.createElement('div');
div.className = 'add-to-car';
var i = document.createElement('i');
i.className = 'iconfont i-jiajianzujianjiahao';
// 设置初始位置
div.style.transform = `translateX(${start.x}px)`;
i.style.transform = `translateY(${start.y}px)`;
div.appendChild(i);
document.body.appendChild(div);
// 强行渲染
div.clientWidth;
// 设置结束位置
div.style.transform = `translateX(${this.jumpTarget.x}px)`;
i.style.transform = `translateY(${this.jumpTarget.y}px)`;
var that = this;
div.addEventListener(
'transitionend',
function () {
div.remove();
that.carAnimate();
},
{
once: true, // 事件仅触发一次
}
);
}
}
分析事件
到这里为止界面上的逻辑已经全部完成,开始添加事件
在这里,为了获取我们到底是点击了哪个,可以添加一个自定义属性来获取index
- 代码如下
var ui = new UI();
// 事件
ui.doms.goodsContainer.addEventListener('click', function (e) {
if (e.target.classList.contains('i-jiajianzujianjiahao')) {
var index = +e.target.getAttribute('index');
ui.increase(index);
} else if (e.target.classList.contains('i-jianhao')) {
var index = +e.target.getAttribute('index');
ui.decrease(index);
}
});
window.addEventListener('keypress', function (e) {
if (e.code === 'Equal') {
ui.increase(0);
} else if (e.code === 'Minus') {
ui.decrease(0);
}
});
到这里,就实现了购物车的全部效果,我会上传我的资源,大家自行下载。