目录
- 购物车效果
- 分析数据
- 单件商品的数据
- 整个界面的数据
- 分析界面
- 分析事件
购物车效果
先准备好原始数据和素材
在下面数据的基础上,编写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;}// 商品选择数量+1increase(){this.choose++;}// 商品选择数量-1decrease(){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);}});
到这里,就实现了购物车的全部效果,我会上传我的资源,大家自行下载。