<template lang="pug">
.wrapper
  .screen.-left
    .app-bar
     img.logo(src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1315882/pngwave.png")
    .title Picked items
    .shop-items
      .item(v-for="item in shopItems")
        .item-block
          .image-area(:style="{backgroundColor: item.color}")
            img.image(:src="item.image")
          .name {{ item.name }}
          .description {{ item.description }}
          .bottom-area
            .price ${{ item.price }}
            .button(@click="addToCart(item)" :ref="'addButton' + item.id" :class="{'-active': item.inCart}")
              transition(name="buttonText" mode="out-in")
                p(v-if="!item.inCart") ADD TO CART
                .cover(v-else)
                  .check
  .screen.-right(ref="cartItems")
    .app-bar
     img.logo(src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/1315882/pngwave.png")
    .title Your cart
    transition(name="noContent")
      .no-content(v-if="$data.cartItems.length === 0")
        p.text Your cart is empty.
    .cart-items
      transition-group(name="cartList" tag="div")
        .cart-item(v-for="item in $data.cartItems" :key="item.id")
          .left
            .cart-image
              .image-wrapper
                img.image(:src="item.image")
          .right
            .name {{item.name}}
            .price ${{item.price}}
            .count
              .button(@click="decrement(item)") <
              .number {{item.count}}
              .button(@click="increment(item)") >
    
</template>

<script>
export default {
  data() {
    return {
      shopItems: [],
      cartItems: []
    };
  },
  mounted: function () {
    axios
      .get("https://s3-us-west-2.amazonaws.com/s.cdpn.io/1315882/shoes.json")
      .then((res) => {
        this.$data.shopItems = res.data.shoes;
      });
  },
  methods: {
    addToCart(item) {
      if (!item.inCart) {
        item.inCart = true;
        const newItem = Object.assign({}, item, { count: 1 });
        this.$data.cartItems.push(newItem);

        const animationTarget = this.$refs[`addButton${item.id}`];
        gsap.to(animationTarget, {
          width: 46,
          duration: 0.8,
          ease: "power4"
        });
      }
      this.$nextTick(() => {
        this.$refs.cartItems.scrollTop = this.$refs.cartItems.scrollHeight;
      });
    },

    decrement(item) {
      item.count--;
      const targetShopItem = this.$data.shopItems.find(
        (shopItem) => shopItem.id === item.id
      );

      this.$nextTick(function () {
        if (item.count === 0) {
          const animationTarget = this.$refs[`addButton${targetShopItem.id}`];
          gsap.to(animationTarget, {
            width: 136,
            duration: 0.8,
            ease: "power4"
          });
          targetShopItem.inCart = false;
          const targetIndex = this.$data.cartItems.findIndex(
            (cartItem) => cartItem.id === item.id
          );
          this.$data.cartItems.splice(targetIndex, 1);
        }
      });
    },

    increment(item) {
      item.count++;
    }
  }
};
</script>

<style lang="scss">
$white: #fff;
$yellow: #f6c90e;
$black: #303841;

body {
  font-family: "Rubik", sans-serif;
  color: $black;
}

.wrapper {
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: relative;
  flex-wrap: wrap;
  padding: 40px 20px;
  max-width: 720px;
  margin: 0 auto;

  &::before {
    content: "";
    display: block;
    position: fixed;
    width: 300%;
    height: 100%;
    top: 50%;
    left: 50%;
    border-radius: 100%;
    transform: translateX(-50%) skewY(-8deg);
    background-color: $yellow;
    z-index: -1;
    animation: wave 8s ease-in-out infinite alternate;
  }

  @keyframes wave {
    0% {
      transform: translateX(-50%) skew(0deg, -8deg);
    }

    100% {
      transform: translateX(-30%) skew(8deg, -4deg);
    }
  }
}

.screen {
  background-color: $white;
  box-sizing: border-box;
  width: 340px;
  height: 600px;
  box-shadow: 0 3.2px 2.2px rgba(0, 0, 0, 0.02),
    0 7px 5.4px rgba(0, 0, 0, 0.028), 0 12.1px 10.1px rgba(0, 0, 0, 0.035),
    0 19.8px 18.1px rgba(0, 0, 0, 0.042), 0 34.7px 33.8px rgba(0, 0, 0, 0.05),
    0 81px 81px rgba(0, 0, 0, 0.07);
  border-radius: 30px;
  overflow-y: scroll;
  padding: 0 28px;
  position: relative;
  margin-bottom: 20px;

  &::before {
    content: "";
    display: block;
    position: absolute;
    width: 300px;
    height: 300px;
    border-radius: 100%;
    background-color: $yellow;
    top: -20%;
    left: -50%;
    z-index: 0;
  }

  &::-webkit-scrollbar {
    display: none;
  }

  > .title {
    font-size: 24px;
    font-weight: bold;
    margin: 20px 0;
    position: relative;
  }
}

.app-bar {
  padding: 12px 0;
  position: relative;

  > .logo {
    display: block;
    width: 50px;
  }
}

.shop-items {
  position: relative;
}

.item-block {
  padding: 40px 0 70px;

  &:first-child {
    padding-top: 0;
  }

  > .image-area {
    border-radius: 30px;
    height: 380px;
    display: flex;
    align-items: center;
    overflow: hidden;

    > .image {
      display: block;
      width: 100%;
      filter: drop-shadow(0 30px 20px rgba(0, 0, 0, 0.2));
      transform: rotate(-24deg);
      margin-left: -16px;
    }
  }

  > .name {
    font-size: 20px;
    font-weight: bold;
    margin: 26px 0 20px;
    line-height: 1.5;
  }

  > .description {
    font-size: 13px;
    color: #777;
    line-height: 1.8;
    margin-bottom: 20px;
  }

  > .bottom-area {
    display: flex;
    justify-content: space-between;
    align-items: center;

    > .price {
      font-size: 18px;
      font-weight: bold;
    }

    > .button {
      cursor: pointer;
      background-color: $yellow;
      font-weight: bold;
      font-size: 14px;
      box-sizing: border-box;
      height: 46px;
      padding: 16px 20px;
      border-radius: 100px;
      box-shadow: 0 3px 6px rgba(0, 0, 0, 0.2);
      transition: box-shadow 0.4s, background-color 0.2s;
      user-select: none;
      white-space: nowrap;
      position: relative;
      display: flex;
      align-items: center;
      overflow: hidden;

      &:hover {
        background-color: lighten($yellow, 10%);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
      }

      &.-active {
        pointer-events: none;
        cursor: default;
      }

      > .cover {
        width: 16px;
        height: 16px;
        position: absolute;
        display: flex;
        justify-content: center;
        align-items: center;

        > .check {
          width: 100%;
          height: 100%;
          transform: translate(-100%, -73%) rotate(-45deg);
          position: absolute;
          left: 50%;
          top: 50%;

          &::before,
          &::after {
            content: "";
            display: block;
            background-color: $black;
            position: absolute;
            left: 0;
            bottom: 0;
            border-radius: 10px;
          }

          &::before {
            width: 3px;
            height: 50%;
          }

          &::after {
            width: 100%;
            height: 3px;
          }
        }
      }
    }
  }
}

.cart-items {
  position: relative;
}

.no-content {
  position: relative;

  > .text {
    font-size: 14px;
  }
}

.cart-item {
  display: flex;
  padding: 20px 0;

  > .right {
    > .name {
      font-size: 14px;
      font-weight: bold;
      line-height: 1.5;
      margin-bottom: 10px;
    }

    > .price {
      font-size: 20px;
      font-weight: bold;
      margin-bottom: 16px;
    }

    > .count {
      display: flex;
      align-items: center;

      > .number {
        font-size: 14px;
        margin: 0 14px;
        width: 20px;
        text-align: center;
      }

      .button {
        cursor: pointer;
        width: 28px;
        height: 28px;
        border-radius: 100%;
        background-color: #eee;
        font-size: 16px;
        font-weight: bold;
        display: flex;
        justify-content: center;
        align-items: center;
        transition: 0.2s;
        user-select: none;

        &:hover {
          background-color: #ddd;
        }
      }
    }
  }
}

.cart-image {
  width: 90px;
  height: 90px;
  border-radius: 100%;
  background-color: #eee;
  margin-right: 34px;

  > .image-wrapper {
    > .image {
      display: block;
      width: 140%;
      transform: rotate(-28deg) translateY(-40px);
      filter: drop-shadow(0 30px 20px rgba(0, 0, 0, 0.2));
    }
  }
}

.buttonText-leave-active,
.buttonText-enter-active {
  transition: opacity 0.2s, top 0.35s;
}
.buttonText-leave-to,
.buttonText-enter {
  opacity: 0;
}

.cartList-enter-active {
  transition: all 2s;

  > .right {
    > .name,
    > .price {
      transition: 0.4s;
    }

    > .name {
      transition-delay: 0.7s;
    }

    > .price {
      transition-delay: 0.85s;
    }

    > .count {
      transition: opacity 0.4s;
      transition-delay: 1s;
    }
  }

  .cart-image {
    transition: 0.5s cubic-bezier(0.79, 0.01, 0.22, 1);

    > .image-wrapper {
      transition: 0.5s cubic-bezier(0.79, 0.01, 0.22, 1) 0.1s;
    }
  }
}

.cartList {
  &-enter {
    > .right {
      > .name,
      > .price {
        opacity: 0;
        transform: translateX(30px);
      }

      .count {
        opacity: 0;
      }
    }

    .cart-image {
      transform: scale(0);

      > .image-wrapper {
        transform: scale(0);
      }
    }
  }

  &-leave-active {
    transition: 0.7s cubic-bezier(0.79, 0.01, 0.22, 1);
    position: absolute;
  }

  &-leave-to {
    transform: scale(0);
    opacity: 0;
  }

  &-move {
    transition: 0.7s cubic-bezier(0.79, 0.01, 0.22, 1);
  }
}

.noContent {
  &-enter-active,
  &-leave-active {
    transition: opacity 0.5s;
    position: absolute;
  }

  &-enter,
  &-leave-to {
    opacity: 0;
  }
}
</style>
Run Pen

External CSS

  1. https://fonts.googleapis.com/css2?family=Rubik:wght@300;700&amp;display=swap

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.6/gsap.min.js