老套的购物车效果

发布时间:2024年01月23日

在这里插入图片描述
在这里插入图片描述

一、项目文件结构

在这里插入图片描述

二、各个文件内容

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>外卖</title>
    <link rel="shortcut icon" href="./assets/favicon.ico" type="image/x-icon" />
    <link rel="stylesheet" href="./css/common.css" />
    <link rel="stylesheet" href="./css/container.css" />
    <link rel="stylesheet" href="./css/footer.css" />
    <link rel="stylesheet" href="./css/add-to-car.css" />
  </head>
  <body>
    <div class="container">
      <div class="menu">
        <div class="menu-item active"><span>推荐</span></div>
        <div class="menu-item"><span>热销</span></div>
        <div class="menu-item"><span>折扣</span></div>
        <div class="menu-item"><span>夏日冰咖必喝榜</span></div>
        <div class="menu-item"><span>进店必喝</span></div>
        <div class="menu-item"><span>只喝美式</span></div>
        <div class="menu-item"><span>酷爽特调水果冰萃</span></div>
        <div class="menu-item"><span>经典奶咖</span></div>
        <div class="menu-item"><span>创意奶咖</span></div>
        <div class="menu-item"><span>瑞纳冰季</span></div>
        <div class="menu-item"><span>不喝咖啡</span></div>
        <div class="menu-item"><span>轻食甜品</span></div>
        <div class="menu-item"><span>热卖套餐</span></div>
      </div>
      <div class="goods-list"></div>
    </div>
    <div class="footer">
      <div class="footer-car-container">
        <div class="footer-car">
          <i class="iconfont i-gouwuchefill"></i>
          <span class="footer-car-badge">0</span>
        </div>
        <div class="footer-car-price">
          <span class="footer-car-unit"></span>
          <span class="footer-car-total">0.00</span>
        </div>
        <div class="footer-car-tip">配送费¥0</div>
      </div>
      <div class="footer-pay">
        <a href="">去结算</a>
        <span>还差¥0元起送</span>
      </div>
    </div>

    <!-- <div class="add-to-car">
      <i class="iconfont i-jiajianzujianjiahao"></i>
    </div> -->

    <script src="./js/data.js"></script>
    <script src="./js/index.js"></script>
  </body>
</html>

js/index.js

// 单件商品的数据
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() {
    if (this.choose === 0) {
      return;
    }
    this.choose--;
  }
}

// 整个界面的数据
class UIData {
  constructor() {
    var uiGoods = [];
    for (var i = 0; i < goods.length; i++) {
      var uig = new UIGoods(goods[i]);
      uiGoods.push(uig);
    }
    this.uiGoods = uiGoods;
    this.deliveryThreshold = 30;
    this.deliveryPrice = 5;
  }

  getTotalPrice() {
    var sum = 0;
    for (var i = 0; i < this.uiGoods.length; i++) {
      var 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 (var 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();
  }
}

// 整个界面
class UI {
  constructor() {
    this.uiData = new UIData();
    this.doms = {
      goodsContainer: document.querySelector('.goods-list'),
      deliveryPrice: 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() {
    var html = '';
    for (var 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.deliveryPrice.textContent = `配送费¥${this.uiData.deliveryPrice}`;
    // 设置起送费还差多少
    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, // 事件仅触发一次
      }
    );
  }
}

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);
  }
});

js/data.js

var goods = [
  {
    pic: './assets/g1.png',
    title: '椰云拿铁',
    desc: `1人份【年度重磅,一口吞云】
    √原创椰云topping,绵密轻盈到飞起!
    原创瑞幸椰云?工艺,使用椰浆代替常规奶盖
    打造丰盈、绵密,如云朵般细腻奶沫体验
    椰香清甜饱满,一口滑入口腔
    
    【饮用建议】请注意不要用吸管,不要搅拌哦~`,
    sellNumber: 200,
    favorRate: 95,
    price: 32,
  },
  {
    pic: './assets/g2.png',
    title: '生椰拿铁',
    desc: `1人份【YYDS,无限回购】
    现萃香醇Espresso,遇见优质冷榨生椰浆,椰香浓郁,香甜清爽,带给你不一样的拿铁体验!
    
    主要原料:浓缩咖啡、冷冻椰浆、原味调味糖浆
    图片及包装仅供参考,请以实物为准。建议送达后尽快饮用。到店饮用口感更佳。`,
    sellNumber: 1000,
    favorRate: 100,
    price: 19.9,
  },
  {
    pic: './assets/g3.png',
    title: '加浓 美式',
    desc: `1人份【清醒加倍,比标美多一份Espresso】
    口感更佳香醇浓郁,回味持久
    图片仅供参考,请以实物为准。建议送达后尽快饮用。`,
    sellNumber: 200,
    favorRate: 93,
    price: 20.3,
  },
  {
    pic: './assets/g4.png',
    title: '瓦尔登蓝钻瑞纳冰',
    desc: `1人份【爆款回归!蓝色治愈力量】
    灵感来自下澄明、碧蓝之境---瓦尔登湖。含藻蓝蛋白,梦幻蓝色源自天然植物成分,非人工合成色素,融入人气冷榨生椰浆,椰香浓郁,清冽冰爽;底部添加Q弹小料,0脂原味晶球,光泽剔透,如钻石般blingbling。搭配奶油顶和彩虹色棉花糖,满足你的少女心~
    【去奶油小提示】由于去掉奶油后顶料口味会受影响,为保证口感,选择“去奶油”选项时将同时去掉奶油及顶料,请注意哦!【温馨提示】瑞纳冰系列产品形态为冰沙,无法进行少冰、去冰操作,请您谅解。【图片仅供参考,请以实物为准】`,
    sellNumber: 17,
    favorRate: 80,
    price: 38,
  },
  {
    pic: './assets/g5.png',
    title: '椰云精萃美式',
    desc: `1人份【不用吸管 大口吞云!】

    1杯热量*≈0.6个苹果!
    原创瑞幸椰云?工艺,将「椰浆」变成绵密、丰盈的“云朵”,口感绵密顺滑!0乳糖植物基,清爽轻负担!
    
    *数据引自《中国食物成分表》第六版,苹果每100克可食部分中能量约为53千卡,以每个苹果250克/个计,1杯椰云精萃美式约80千卡,相当于约0.6个苹果。
    【图片仅供参考,请以实物为准】`,
    sellNumber: 50,
    favorRate: 90,
    price: 21.12,
  },
];

css/footer.css

.footer {
  height: 100rem;
  color: #fff;
  position: fixed;
  left: 0;
  bottom: 0;
  width: 100%;
  display: flex;
  z-index: 10;
}

.footer-car-container {
  flex-grow: 1;
  background: #3d3d3f;
  padding-left: 175rem;
  position: relative;
}
.footer-car {
  position: absolute;
  width: 118rem;
  height: 118rem;
  border: 9rem solid #444;
  left: 25rem;
  top: -35rem;
  border-radius: 50%;
  background: inherit;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 60rem;
}
.footer-car-badge {
  position: absolute;
  width: 35rem;
  height: 35rem;
  background: #ec5533;
  font-size: 25rem;
  text-align: center;
  border-radius: 50%;
  line-height: 35rem;
  right: 0;
  top: 0;
  display: none;
}
.footer-car.active {
  background: #4a90e1;
}
.footer-car.active .footer-car-badge {
  display: block;
}
.footer-car.animate {
  animation: footer-car-animate 500ms ease-in-out;
}

@keyframes footer-car-animate {
  0% {
    transform: scale(1);
  }
  25% {
    transform: scale(0.8);
  }
  50% {
    transform: scale(1.1);
  }
  75% {
    transform: scale(0.9);
  }
  100% {
    transform: scale(1);
  }
}

.footer-car-price {
  font-size: 40rem;
  display: flex;
  margin-top: 5rem;
}
.footer-car-tip {
  font-size: 20rem;
  margin-left: 6rem;
}
.footer-pay {
  background: #535356;
  width: 250rem;
  font-size: 35rem;
  line-height: 100rem;
  text-align: center;
}
.footer-pay > * {
  display: block;
  width: 100%;
  height: 100%;
}
.footer-pay a {
  display: none;
}
.footer-pay span {
  font-size: 30rem;
}
.footer-pay.active {
  background: #76d572;
}
.footer-pay.active a {
  display: block;
}
.footer-pay.active span {
  display: none;
}

css/add-to-car.css

.add-to-car {
  position: fixed;
  color: #fff;
  font-size: 23rem;
  line-height: 40rem;
  text-align: center;
  z-index: 9;
  margin-left: -20rem;
  margin-top: -20rem;
  left: 0;
  top: 0;
  transition: 0.5s linear;
}

.add-to-car .iconfont {
  width: 40rem;
  height: 40rem;
  background: #4a90e1;
  border-radius: 50%;
  display: block;
  transition: 0.5s cubic-bezier(0.5, -0.5, 1, 1);
}

css/container.css

.container {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: calc(100% - 100rem);
  display: flex;
  color: #333;
}
.menu {
  background: #f5f5f5;
  width: 190rem;
  overflow-y: scroll;
  padding-bottom: 50rem;
  flex: 0 0 auto;
}
.menu::-webkit-scrollbar {
  width: 0;
}

.menu-item {
  height: 141rem;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 0 30rem;
  position: relative;
}
.menu-item span {
  font-size: 30rem;
  line-height: 40rem;
  max-height: 80rem;
  overflow: hidden;
}
.menu-item.active {
  font-weight: bold;
  background: #fff;
}
.menu-item.active::before {
  content: '';
  position: absolute;
  left: 0;
  height: 100%;
  width: 7.5rem;
  background: #3190e8;
}

.goods-list {
  flex-grow: 1;
  overflow-y: scroll;
}
.goods-list::-webkit-scrollbar {
  width: 0;
}

.goods-item {
  border-bottom: 1rem solid #f8f8f8;
  padding: 30rem 20rem;
  display: flex;
}
.goods-pic {
  width: 200rem;
  height: 200rem;
  object-fit: contain;
  border: 1rem solid rgba(0, 0, 0, 0.06);
  flex: 0 0 auto;
}
.goods-info {
  flex: 1 1 auto;
  padding: 0 35rem;
  overflow: hidden;
}
.goods-title {
  font-size: 35rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-bottom: 20rem;
}
.goods-desc {
  font-size: 24rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 20rem;
}
.goods-sell {
  color: #858687;
  font-size: 24rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 20rem;
  display: flex;
}
.goods-sell span:first-child {
  margin-right: 18rem;
}
.goods-confirm {
  display: flex;
  justify-content: space-between;
}
.goods-price {
  display: flex;
  font-size: 35rem;
  font-weight: bold;
  color: #f60;
  align-items: flex-end;
}
.goods-price-unit {
  font-size: 25rem;
  margin-bottom: 4rem;
  font-weight: normal;
}
.goods-btns {
  display: flex;
  justify-content: center;
  align-items: center;
}
.goods-btns .iconfont {
  width: 40rem;
  height: 40rem;
  background: #4a90e1;
  color: #fff;
  border-radius: 50%;
  font-size: 23rem;
  line-height: 40rem;
  text-align: center;
}
.goods-btns span {
  margin: 0 15rem;
  display: none;
}
.goods-btns .i-jianhao {
  border: 1rem solid #4a90e1;
  background: #fff;
  color: #4a90e1;
  font-weight: bold;
  display: none;
}
.goods-item.active span {
  display: block;
}
.goods-item.active .i-jianhao {
  display: block;
}

css/common.css

@import url('https://at.alicdn.com/t/c/font_3555577_me2a6tdmvu8.css');

*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  font-size: 0.125vw;
}
body {
  font-size: 35rem;
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
  user-select: none;
  font-family: 'Microsoft Yahei', 'sans-serif';
}
a {
  color: inherit;
  text-decoration: none;
}
.iconfont {
  font-size: inherit;
}
文章来源:https://blog.csdn.net/qq_43206280/article/details/135770105
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。