JAVASCRIPTshopifyintermediate

Shopify Slide-Out Cart Drawer

Create a modern slide-out cart drawer with AJAX functionality

#shopify#cart#ajax#drawer#javascript
Share this snippet:

Code

javascript
1// Cart Drawer Functionality
2class CartDrawer {
3 constructor() {
4 this.drawer = document.querySelector('[data-cart-drawer]');
5 this.overlay = document.querySelector('[data-cart-overlay]');
6 this.closeBtn = document.querySelector('[data-cart-close]');
7 this.cartCount = document.querySelector('[data-cart-count]');
8
9 this.init();
10 }
11
12 init() {
13 // Bind events
14 document.addEventListener('click', (e) => {
15 if (e.target.matches('[data-cart-trigger]')) {
16 e.preventDefault();
17 this.open();
18 }
19 });
20
21 if (this.closeBtn) {
22 this.closeBtn.addEventListener('click', () => this.close());
23 }
24
25 if (this.overlay) {
26 this.overlay.addEventListener('click', () => this.close());
27 }
28
29 // Update cart on page load
30 this.updateCart();
31 }
32
33 open() {
34 this.drawer.classList.add('is-active');
35 this.overlay.classList.add('is-active');
36 document.body.style.overflow = 'hidden';
37 this.updateCart();
38 }
39
40 close() {
41 this.drawer.classList.remove('is-active');
42 this.overlay.classList.remove('is-active');
43 document.body.style.overflow = '';
44 }
45
46 async updateCart() {
47 try {
48 const response = await fetch('/cart.js');
49 const cart = await response.json();
50
51 this.renderCart(cart);
52 this.updateCartCount(cart.item_count);
53 } catch (error) {
54 console.error('Error updating cart:', error);
55 }
56 }
57
58 renderCart(cart) {
59 const cartItems = document.querySelector('[data-cart-items]');
60 const cartTotal = document.querySelector('[data-cart-total]');
61 const emptyMessage = document.querySelector('[data-cart-empty]');
62
63 if (cart.items.length === 0) {
64 cartItems.innerHTML = '';
65 emptyMessage.style.display = 'block';
66 return;
67 }
68
69 emptyMessage.style.display = 'none';
70
71 const itemsHTML = cart.items.map(item => `
72 <div class="cart-item" data-line-item="${item.key}">
73 <div class="cart-item__image">
74 <img src="${item.featured_image.url}" alt="${item.product_title}">
75 </div>
76 <div class="cart-item__details">
77 <h4>${item.product_title}</h4>
78 ${item.variant_title ? `<p>${item.variant_title}</p>` : ''}
79 <div class="cart-item__price">
80 ${this.formatMoney(item.final_line_price)}
81 </div>
82 </div>
83 <div class="cart-item__quantity">
84 <button class="qty-btn" data-cart-decrease="${item.key}">-</button>
85 <input type="number" value="${item.quantity}" min="0" data-cart-quantity="${item.key}">
86 <button class="qty-btn" data-cart-increase="${item.key}">+</button>
87 </div>
88 <button class="cart-item__remove" data-cart-remove="${item.key}">
89 Remove
90 </button>
91 </div>
92 `).join('');
93
94 cartItems.innerHTML = itemsHTML;
95 cartTotal.textContent = this.formatMoney(cart.total_price);
96
97 this.bindCartEvents();
98 }
99
100 bindCartEvents() {
101 // Quantity decrease
102 document.querySelectorAll('[data-cart-decrease]').forEach(btn => {
103 btn.addEventListener('click', (e) => {
104 const key = e.target.dataset.cartDecrease;
105 const input = document.querySelector(`[data-cart-quantity="${key}"]`);
106 const newQty = parseInt(input.value) - 1;
107 if (newQty >= 0) {
108 this.updateLineItem(key, newQty);
109 }
110 });
111 });
112
113 // Quantity increase
114 document.querySelectorAll('[data-cart-increase]').forEach(btn => {
115 btn.addEventListener('click', (e) => {
116 const key = e.target.dataset.cartIncrease;
117 const input = document.querySelector(`[data-cart-quantity="${key}"]`);
118 const newQty = parseInt(input.value) + 1;
119 this.updateLineItem(key, newQty);
120 });
121 });
122
123 // Direct quantity input
124 document.querySelectorAll('[data-cart-quantity]').forEach(input => {
125 input.addEventListener('change', (e) => {
126 const key = e.target.dataset.cartQuantity;
127 const newQty = parseInt(e.target.value);
128 this.updateLineItem(key, newQty);
129 });
130 });
131
132 // Remove item
133 document.querySelectorAll('[data-cart-remove]').forEach(btn => {
134 btn.addEventListener('click', (e) => {
135 const key = e.target.dataset.cartRemove;
136 this.updateLineItem(key, 0);
137 });
138 });
139 }
140
141 async updateLineItem(key, quantity) {
142 try {
143 const response = await fetch('/cart/change.js', {
144 method: 'POST',
145 headers: {
146 'Content-Type': 'application/json',
147 },
148 body: JSON.stringify({
149 id: key,
150 quantity: quantity
151 })
152 });
153
154 const cart = await response.json();
155 this.renderCart(cart);
156 this.updateCartCount(cart.item_count);
157 } catch (error) {
158 console.error('Error updating cart:', error);
159 }
160 }
161
162 updateCartCount(count) {
163 if (this.cartCount) {
164 this.cartCount.textContent = count;
165 this.cartCount.style.display = count > 0 ? 'flex' : 'none';
166 }
167 }
168
169 formatMoney(cents) {
170 return '$' + (cents / 100).toFixed(2);
171 }
172}
173
174// Initialize
175document.addEventListener('DOMContentLoaded', () => {
176 new CartDrawer();
177});

Shopify Slide-Out Cart Drawer

Create a modern slide-out cart drawer that opens from the side with smooth animations and updates in real-time without page reloads.

// Cart Drawer Functionality
class CartDrawer {
    constructor() {
        this.drawer = document.querySelector('[data-cart-drawer]');
        this.overlay = document.querySelector('[data-cart-overlay]');
        this.closeBtn = document.querySelector('[data-cart-close]');
        this.cartCount = document.querySelector('[data-cart-count]');

        this.init();
    }

    init() {
        // Bind events
        document.addEventListener('click', (e) => {
            if (e.target.matches('[data-cart-trigger]')) {
                e.preventDefault();
                this.open();
            }
        });

        if (this.closeBtn) {
            this.closeBtn.addEventListener('click', () => this.close());
        }

        if (this.overlay) {
            this.overlay.addEventListener('click', () => this.close());
        }

        // Update cart on page load
        this.updateCart();
    }

    open() {
        this.drawer.classList.add('is-active');
        this.overlay.classList.add('is-active');
        document.body.style.overflow = 'hidden';
        this.updateCart();
    }

    close() {
        this.drawer.classList.remove('is-active');
        this.overlay.classList.remove('is-active');
        document.body.style.overflow = '';
    }

    async updateCart() {
        try {
            const response = await fetch('/cart.js');
            const cart = await response.json();

            this.renderCart(cart);
            this.updateCartCount(cart.item_count);
        } catch (error) {
            console.error('Error updating cart:', error);
        }
    }

    renderCart(cart) {
        const cartItems = document.querySelector('[data-cart-items]');
        const cartTotal = document.querySelector('[data-cart-total]');
        const emptyMessage = document.querySelector('[data-cart-empty]');

        if (cart.items.length === 0) {
            cartItems.innerHTML = '';
            emptyMessage.style.display = 'block';
            return;
        }

        emptyMessage.style.display = 'none';

        const itemsHTML = cart.items.map(item => `
            <div class="cart-item" data-line-item="${item.key}">
                <div class="cart-item__image">
                    <img src="${item.featured_image.url}" alt="${item.product_title}">
                </div>
                <div class="cart-item__details">
                    <h4>${item.product_title}</h4>
                    ${item.variant_title ? `<p>${item.variant_title}</p>` : ''}
                    <div class="cart-item__price">
                        ${this.formatMoney(item.final_line_price)}
                    </div>
                </div>
                <div class="cart-item__quantity">
                    <button class="qty-btn" data-cart-decrease="${item.key}">-</button>
                    <input type="number" value="${item.quantity}" min="0" data-cart-quantity="${item.key}">
                    <button class="qty-btn" data-cart-increase="${item.key}">+</button>
                </div>
                <button class="cart-item__remove" data-cart-remove="${item.key}">
                    Remove
                </button>
            </div>
        `).join('');

        cartItems.innerHTML = itemsHTML;
        cartTotal.textContent = this.formatMoney(cart.total_price);

        this.bindCartEvents();
    }

    bindCartEvents() {
        // Quantity decrease
        document.querySelectorAll('[data-cart-decrease]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const key = e.target.dataset.cartDecrease;
                const input = document.querySelector(`[data-cart-quantity="${key}"]`);
                const newQty = parseInt(input.value) - 1;
                if (newQty >= 0) {
                    this.updateLineItem(key, newQty);
                }
            });
        });

        // Quantity increase
        document.querySelectorAll('[data-cart-increase]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const key = e.target.dataset.cartIncrease;
                const input = document.querySelector(`[data-cart-quantity="${key}"]`);
                const newQty = parseInt(input.value) + 1;
                this.updateLineItem(key, newQty);
            });
        });

        // Direct quantity input
        document.querySelectorAll('[data-cart-quantity]').forEach(input => {
            input.addEventListener('change', (e) => {
                const key = e.target.dataset.cartQuantity;
                const newQty = parseInt(e.target.value);
                this.updateLineItem(key, newQty);
            });
        });

        // Remove item
        document.querySelectorAll('[data-cart-remove]').forEach(btn => {
            btn.addEventListener('click', (e) => {
                const key = e.target.dataset.cartRemove;
                this.updateLineItem(key, 0);
            });
        });
    }

    async updateLineItem(key, quantity) {
        try {
            const response = await fetch('/cart/change.js', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    id: key,
                    quantity: quantity
                })
            });

            const cart = await response.json();
            this.renderCart(cart);
            this.updateCartCount(cart.item_count);
        } catch (error) {
            console.error('Error updating cart:', error);
        }
    }

    updateCartCount(count) {
        if (this.cartCount) {
            this.cartCount.textContent = count;
            this.cartCount.style.display = count > 0 ? 'flex' : 'none';
        }
    }

    formatMoney(cents) {
        return '$' + (cents / 100).toFixed(2);
    }
}

// Initialize
document.addEventListener('DOMContentLoaded', () => {
    new CartDrawer();
});

HTML Structure (Liquid)

<!-- Cart Drawer -->
<div class="cart-drawer" data-cart-drawer>
    <div class="cart-drawer__header">
        <h2>Your Cart</h2>
        <button class="cart-drawer__close" data-cart-close>
            <svg><!-- Close icon --></svg>
        </button>
    </div>

    <div class="cart-drawer__body">
        <div class="cart-items" data-cart-items>
            <!-- Cart items will be rendered here -->
        </div>

        <div class="cart-empty" data-cart-empty style="display: none;">
            <p>Your cart is empty</p>
            <a href="/collections/all" class="btn">Continue Shopping</a>
        </div>
    </div>

    <div class="cart-drawer__footer">
        <div class="cart-total">
            <span>Total:</span>
            <span data-cart-total>$0.00</span>
        </div>
        <a href="/checkout" class="btn btn--checkout">Checkout</a>
    </div>
</div>

<!-- Overlay -->
<div class="cart-overlay" data-cart-overlay></div>

<!-- Cart Trigger -->
<a href="/cart" data-cart-trigger>
    Cart (<span data-cart-count>{{ cart.item_count }}</span>)
</a>

CSS Styling

.cart-drawer {
    position: fixed;
    top: 0;
    right: -400px;
    width: 400px;
    height: 100vh;
    background: #fff;
    box-shadow: -2px 0 10px rgba(0,0,0,0.1);
    transition: right 0.3s ease;
    z-index: 1001;
    display: flex;
    flex-direction: column;
}

.cart-drawer.is-active {
    right: 0;
}

.cart-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.5);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s ease, visibility 0.3s ease;
    z-index: 1000;
}

.cart-overlay.is-active {
    opacity: 1;
    visibility: visible;
}

.cart-drawer__header {
    padding: 20px;
    border-bottom: 1px solid #eee;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.cart-drawer__body {
    flex: 1;
    overflow-y: auto;
    padding: 20px;
}

.cart-item {
    display: grid;
    grid-template-columns: 80px 1fr auto;
    gap: 15px;
    padding: 15px 0;
    border-bottom: 1px solid #eee;
}

.cart-item__image img {
    width: 100%;
    height: auto;
    object-fit: cover;
}

.cart-item__quantity {
    display: flex;
    align-items: center;
    gap: 10px;
}

.qty-btn {
    width: 30px;
    height: 30px;
    border: 1px solid #ddd;
    background: #fff;
    cursor: pointer;
}

.cart-drawer__footer {
    padding: 20px;
    border-top: 1px solid #eee;
}

.cart-total {
    display: flex;
    justify-content: space-between;
    font-size: 18px;
    font-weight: bold;
    margin-bottom: 15px;
}

.btn--checkout {
    width: 100%;
    padding: 15px;
    background: #000;
    color: #fff;
    text-align: center;
    text-decoration: none;
    display: block;
}

Features

  • Slide-Out Animation: Smooth drawer animation
  • Real-Time Updates: AJAX cart updates without reload
  • Quantity Controls: Increase/decrease quantities
  • Remove Items: Quick item removal
  • Cart Count Badge: Dynamic cart count display
  • Responsive: Works on all devices
  • Accessible: Keyboard and screen reader friendly

Related Snippets