Web前端—(原生JS)购物车效果

目录

购物车效果

先准备好原始数据和素材

在下面数据的基础上,编写index.js

分析数据

  • 编写程序要从数据入手,从数据到界面最后到事件
  • 在分析数据的过程中,要分析数据是通过属性出现还是通过方法出现

单件商品的数据

  • 我们观察data.js中的数据:商品数组goods

    • pic:图片
    • title:标题
    • desc:描述
    • sellNumber:月售
    • favorRate:好评率
    • price:价格
  • 为了避免改变原始数据。我们创建一个单件商品的数据的类(class)

  • 代码如下:
javascript 复制代码
// 单件商品的数据
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类,并进行测试(大家可以自行在控制台进行测试)

  • 代码如下
javascript 复制代码
// 整个界面的数据
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类

  • 代码如下:
javascript 复制代码
// 整个界面
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

  • 代码如下
javascript 复制代码
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);
    }
  });

到这里,就实现了购物车的全部效果,我会上传我的资源,大家自行下载。

相关推荐
赵大仁15 分钟前
深入理解 Vue 3 中的具名插槽
前端·javascript·vue.js·react.js·前端框架·ecmascript·html5
一雨方知深秋20 分钟前
v-bind 操作 class(对象,数组),v-bind 操作 style
前端·css·vue.js·html·style·class·v-bind
安晴晚风1 小时前
从0开始在linux服务器上部署SpringBoot和Vue
linux·运维·前端·数据库·后端·运维开发
前端小小王2 小时前
pnpm、Yarn 和 npm 的区别?
前端·npm·node.js
supermapsupport2 小时前
使用npm包的工程如何引入mapboxgl-enhance/maplibre-gl-enhance扩展包
前端·webpack·npm·supermap·mapboxgl
牛奔2 小时前
windows nvm 切换node版本后,npm找不到
前端·windows·npm·node.js
鱼大大博客2 小时前
Edge SCDN酷盾安全重塑高效安全内容分发新生态
前端·安全·edge
鸭梨山大。3 小时前
NPM组件包 vant部分版本内嵌挖矿代码
前端·安全·npm·node.js·vue
蟾宫曲7 小时前
在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)
前端·npm·vue3·vite·element-plus·计时器
秋雨凉人心7 小时前
简单发布一个npm包
前端·javascript·webpack·npm·node.js