Sticky Add to Cart Button V2

Step 1: Create a stickyAddToCart.js

Create a file on assets/js/theme/custom directory.

export default class StickyAddToCart {

    constructor() {
        this.change         = new Event('change', { bubbles: true });
        this.prefix         = 'opt7_';

        this.adcWrapper     = document.getElementById("add-to-cart-wrapper");
        this.opt7Wraper     = document.getElementById("opt7-quantity-box");
        this.stickyElement  = document.getElementById('opt7_sticky_adc');
        this.qtyInput       = this.adcWrapper.querySelector('input');

        this.handleVisible = this.handleVisible.bind(this);
    }

    isElementInViewport() {
        let el = this.adcWrapper;

        if (typeof jQuery === "function" && el instanceof jQuery) { el = el[0]; }

        let rect = el.getBoundingClientRect();

        return (
            rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth)
        );
    }

    handleVisible() {

        if (!this.isElementInViewport()) {
            this.stickyElement.style.display = 'block';
        } else {
            this.stickyElement.style.display = 'none';
        }

    }

    updateQty(btn, e) {
        e.preventDefault();

        const target = btn.getAttribute('data-action');
        const duplicateInput = document.getElementById(`${this.prefix}${this.qtyInput.getAttribute('id')}`);

        const quantityMin = parseInt(this.qtyInput.getAttribute('data-quantity-min'), 10);
        const quantityMax = parseInt(this.qtyInput.getAttribute('data-quantity-max'), 10);

        let qty = parseInt(this.qtyInput.value, 10);

        // If action is incrementing
        if (target === 'inc') {
            // If quantity max option is set
            if (quantityMax > 0) {
                // Check quantity does not exceed max
                if ((qty + 1) <= quantityMax) {
                    qty++;
                }
            } else {
                qty++;
            }
        } else if (qty > 1) {
            // If quantity min option is set
            if (quantityMin > 0) {
                // Check quantity does not fall below min
                if ((qty - 1) >= quantityMin) {
                    qty--;
                }
            } else {
                qty--;
            }
        }

        this.qtyInput.value = qty;
        duplicateInput.value = qty;
        this.qtyInput.dispatchEvent(this.change);
    }

    duplicateForm() {
        const originalForm= this.adcWrapper.querySelector('.form-field--increments');
        const duplicateForm = originalForm.cloneNode(true);

        duplicateForm.querySelectorAll('[id]').forEach((element) => {
            element.id = this.prefix + element.id;
        });

        duplicateForm.querySelectorAll(`[data-quantity-change] button`).forEach((btn) => {
            btn.addEventListener('click', (e) => {
                this.updateQty(btn, e)
            })
        })

        this.adcWrapper.querySelectorAll(`[data-quantity-change] button`).forEach((btn) => {
            btn.addEventListener('click', (e) => {
                this.qtyInput.dispatchEvent(this.change);
            })
        })

        this.opt7Wraper.append(duplicateForm);
    }

    submitFrom(btn) {

        if (btn.currentTarget.classList.value.match(/hasOptions/gi) !== null) {

            if (window.innerWidth < 500) {
                document.querySelector('.product-options').scrollIntoView({
                    behavior: 'smooth'
                });
            } else {
                document.querySelector('#main-content').scrollIntoView({
                    behavior: 'smooth'
                });
            }

            setTimeout(() => {
                $('#form-action-addToCart').trigger('click');
            }, 700)

        } else {

            document.querySelector('#main-content').scrollIntoView({
                behavior: 'smooth'
            });
            $('#form-action-addToCart').trigger('click');
        }

    }

    init() {
        this.duplicateForm();

        const duplicateInput = document.getElementById(`${this.prefix}${this.qtyInput.getAttribute('id')}`);
        const events = ['change', 'keyup', 'keydown'];
        const quantityInput = this.qtyInput;

        window.addEventListener('load', this.handleVisible);
        window.addEventListener('resize', this.handleVisible);
        window.addEventListener('scroll', this.handleVisible);

        document.querySelector(`#${this.stickyElement.getAttribute('id')} button.atc`).addEventListener('click', (e) => this.submitFrom(e));

        events.forEach(function (event) {
            quantityInput.addEventListener(event, (e) => {
                setTimeout(() => {
                    duplicateInput.value = e.target.value
                }, [100])
            });

            duplicateInput.addEventListener(event, (e) => {
                quantityInput.value = e.target.value;
            });
        });

    }
}

Step 2: Import and Call stickyAddToCart function

Call stickyAddToCart on assets/js/theme/common/product-details.js file's onReady() method.

//......
//......
import stickyAddToCart from '../custom/stickyAddToCart';
//......
//......
export default class ProductDetails extends PageManager {
    constructor(context) {
        super(context);
        //......
    }

    onReady() {
        //......
        //......
        //......
        //......
        const adc = new stickyAddToCart();
        adc.init();
    }
}

Step 3: Add styles on Add to Cart button

Modify this style for your design on _custom.scss file.

.stickyAddToCart--container {
  width            : 100%;
  height           : 70px;
  display          : none;
  position         : fixed;
  z-index          : 999;
  background-color : #FFFFFF;
  bottom           : 0;
  left             : 0;
  padding          : 10px;
  border-top: 1px solid #c3c2c2;
  box-shadow: -28px 10px 80px -19px #000000;

  .product-properties {
    display         : flex;
    flex-direction  : row;
    flex-wrap       : nowrap;
    align-content   : center;
    align-items     : center;
    justify-content : space-between;

    .title {
      flex : 0 1 65%;
      @include breakpoint('xxsmall') {
        flex : 0 1 58%;
      }
      @include breakpoint('medium') {
        flex : 0 1 65%;
      }

      h2 {
        margin      : 0;
        font-size   : 20px;
        font-weight : 500;
        @include breakpoint('xxsmall') {
          font-size : 12px;
        }
        @include breakpoint('medium') {
          font-size : 20px;
        }
      }
    }

    .image {
      flex : 0 1 5%;
      @include breakpoint('xxsmall') {
        flex    : 0 1 10%;
        display : none;
      }
      @include breakpoint('medium') {
        flex    : 0 1 5%;
        display : block;
      }

      img {
        width : 50px;
      }
    }

    .add-to-cart {
      flex           : 0 1 30%;
      display        : flex;
      flex-direction : row;
      align-items    : center;
      @include breakpoint('xxsmall') {
        flex : 0 1 40%;
        justify-content: flex-end;
      }
      @include breakpoint('medium') {
        flex : 0 1 30%;
        justify-content: flex-end;
      } 
      div#opt7-quantity-box {
        flex : 0 1 40%;
        @include breakpoint('xxsmall') {
          flex : 0 1 50%;
        }
        @include breakpoint('medium') {
          flex : 0 1 40%;
        }

        .form-field--increments {
          margin-bottom : 0px;
        }

        .form-increment {
          text-align : initial;
        }

        .form-field--increments .form-increment button, .form-field--increments .form-increment .form-input--incrementTotal {
          border-radius : 0rem;
          padding       : 10px;
          height        : 50px;
          border-color  : rgb(153, 153, 153);
          margin        : -4px;
          @include breakpoint('xxsmall') {
            padding : 5px;
          }
          @include breakpoint('medium') {
            padding : 10px;
          }
        }

        .form-field--increments .form-increment .form-input--incrementTotal {
          border-top    : 1px solid rgb(153, 153, 153);
          border-bottom : 1px solid rgb(153, 153, 153);
          font-size     : 20px;
          font-weight   : bold;
          color         : rgb(0, 0, 0);
          width         : 45px;
          @include breakpoint('xxsmall') {
            width : 32px;
          }
          @include breakpoint('medium') {
            width : 45px;
          }
        }

        .form-field--increments .form-increment [data-action="dec"] {
          border-right : none;
        }

        .form-field--increments .form-increment [data-action="inc"] {
          border-left : none;
        }
      }

      button.atc {
        flex            : 0 1 60%;
        width           : 100%;
        height          : 52px;
        border-radius   : 0rem;
        text-transform  : uppercase;
        font-weight     : bold;
        margin-bottom   : 0px;
        background      : rgb(37, 60, 76);
        appearance      : none;
        border-style    : solid;
        border-width    : 1px;
        cursor          : pointer;
        font-family     : Montserrat, Arial, Helvetica, sans-serif;
        line-height     : normal;
        position        : relative;
        text-align      : center;
        text-decoration : none;
        display         : inline-block;
        font-size       : 1rem;
        padding         : 0.85714rem 2.28571rem;
        outline         : none;
        vertical-align  : middle;
        color           : #FFFFFF;

        @include breakpoint('xxsmall') {
          flex      : 0 1 50%;
          font-size : 14px;
          padding   : 0;
          height    : 50px;
          border    : 1px solid rgb(37, 60, 76);
        }
        @include breakpoint('medium') {
          flex      : 0 1 60%;
          height    : 52px;
          padding   : 0.85714rem 2.28571rem;
          font-size : 1rem;
          border    : 1px solid rgb(37, 60, 76);
        }

        span {
          @include breakpoint('xxsmall') {
            display : none;
          }
          @include breakpoint('medium') {
            display : block;
          }
        }

        i {
          @include breakpoint('xxsmall') {
            display : block;
            font-size: 32px;
          }
          @include breakpoint('medium') {
            display : none;
          }
        }
      }
    }
  }
}

Step 4: Stencil Theme Modify

Modify templates/components/product/product-view.html with this code block at the end of the page.

<div class="stickyAddToCart--container" id="opt7_sticky_adc">
    <div class="product-properties">
        <div class="image">
            {{> components/common/responsive-img
            image=product.main_image
            class="productView-image--default"
            fallback_size=theme_settings.product_size
            lazyload=theme_settings.lazyload_mode
            default_image=theme_settings.default_image_product
            otherAttributes="data-main-image"
            }}
        </div>
        <div class="title"><h2>{{product.title}}</h2></div>
        <div class="add-to-cart">
            <div id="opt7-quantity-box">
                <span class="anchor"></span>
            </div>
            <button {{#if product.options.length '>' 0}} class="atc hasOptions" {{else}} class="atc" {{/if}}>
            <span>Add to cart</span>
            <i class="fa fa-cart-plus"></i>
            </button>
        </div>
    </div>
</div>

Happy Hacking! 🧡