商品の色をボタンで選択できるカートボタンを作成しました【Javascript】

JavaScript

アパレル系ショップで、色やサイズをボタンで選択できるカートをちらほら見かけます。
ユニ●ロやH&●の有名サイトでも採用されているようです。

今回、商品の色をボタンで選択できるカートを作成したのでメモ。

商品の色をボタンで選択できるカートを確認する(デモ)

動作デモ

See the Pen Untitled by donguri2020 (@m-ke) on CodePen.

ソースコードを確認(全体)

HTML

<main class="main">
  <section class="ec-item">
    <h2 style="display: none;">サンプルコード</h2>
    <div class="ec-item__photo">
      <img src="https://m-kenomemo.com/sample/select-cart/images/product.jpg" class="product-photo" alt="製品の写真">
    </div>
    <div class="item__detail">
      <p class="item__detail_name">レザータッチ トートバッグ</p>
      <p class="item__detail_price">¥ 4,890</p>
      <div class="select">
        <div class="select__item">
          <div class="select__img">
            <img src="https://m-kenomemo.com/sample/select-cart/images/item_salmon.jpg" alt="サーモン">
          </div>
          <div class="select__txt">
            <p>サーモン</p>
            <input id="item_salmon" name="item0" type="hidden" value="サーモン(SKU01)">
          </div>
        </div>
        <div class="select__item">
          <div class="select__img">
            <img src="https://m-kenomemo.com/sample/select-cart/images/item_green.jpg" alt="グリーン">
          </div>
          <div class="select__txt">
            <p>グリーン</p>
            <input id="item_green" name="item1" type="hidden" value="グリーン(SKU02)">
          </div>
        </div>
        <div class="select__item">
          <div class="select__img">
            <img src="https://m-kenomemo.com/sample/select-cart/images/item_yellow.jpg" alt="イエロー">
          </div>
          <div class="select__txt">
            <p>イエロー</p>
            <input id="item_yellow" name="item2" type="hidden" value="イエロー(SKU03)">
          </div>
        </div>
        <div class="select__item">
          <div class="select__img">
            <img src="https://m-kenomemo.com/sample/select-cart/images/item_blue.jpg" alt="ブルー">
          </div>
          <div class="select__txt">
            <p>ブルー</p>
            <input id="item_blue" name="item2" type="hidden" value="ブルー(SKU04)">
          </div>
        </div>
      </div>
      <p class="mb5">数量:</p>
      <form action="" method="post" class="cart-example" name="cart-example">
        <select name="select-num" class="selectnum">
          <option value="1">1</option>
          <option value="2">2</option>
          <option value="3">3</option>
          <option value="4">4</option>
          <option value="5">5</option>
        </select>
        <input name="sku-code" type="hidden" value=""> //選択した色を格納する
        <div class="submit">
          <a href="#" class="cart-btn">カートに入れる</a>
        </div>
      </form>
    </div>
  </section><!-- /thumb_thumb-sample -->
</main>

CSS

@media screen and (min-width: 980px) {
  .ec-item {
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
  }
}
.ec-item__photo {
  max-width: 460px;
  margin-left: auto;
  margin-right: auto;
  margin-bottom: 20px;
  flex: 0 1 43%;
}
.item__detail_name {
  font-size: 1.4em;
  font-weight: bold;
  margin-top: 0;
}
.item__detail_price {
  font-size: 1.2em;
  font-weight: bold;
  margin-top: 0;
}
.ec-item__photo img {
  width: 100%;
}
.select {
  max-width: 460px;
  margin-left: auto;
  margin-right: auto;
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
}
.select__item {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  align-items: center;
  border: 1px solid #ccc;
  flex: 0 1 47%;
  box-sizing: border-box;
  padding: 5px 10px;
  margin: 5px;
  cursor: pointer;
}
.select__item.checked {
  position: relative;
}
.select__item.checked::after {
  content: "";
  position: absolute;
  border: 1px solid #795548;
  display: block;
  left: 2px;
  top: 2px;
  width: calc(100% - 6px);
  height: calc(100% - 6px);
  background: rgb(255, 147, 60, 0.3);
}
.select__img {
  flex: 0 1 40px;
  margin-right: 20px;
}
.select__img img {
  width: 100%;
}
.selectnum {
  position: relative;
  padding: 10px;
  min-width: 100%;
}
.selectnum:after {
  content: '<>';
  font: 17px "Consolas", monospace;
  color: #333;
  -webkit-transform: rotate(90deg);
  -moz-transform: rotate(90deg);
  -ms-transform: rotate(90deg);
  transform: rotate(90deg);
  right: 11px;
  top: 18px;
  padding: 0 0 2px;
  border-bottom: 1px solid #999;
  position: absolute;
  pointer-events: none;
}

.selectnum select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  max-width: 320px;
  height: 50px;
  float: right;
  margin: 5px 0px;
  padding: 0px 24px;
  font-size: 16px;
  line-height: 1.75;
  color: #333;
  background-color: #ffffff;
  background-image: none;
  border: 1px solid #cccccc;
  -ms-word-break: normal;
  word-break: normal;
}

.submit{
  width:100%;
  height:60px;
  line-height:60px;
  margin-top: 50px;
  }
  .submit a{
    display:block;
    width:100%;
    height:100%;
    text-decoration: none;
    background:#FF0000;
    text-align:center;
    color:#FFFFFF;
    font-size:20px;
    font-weight:bold;
    transition: all 0.4s ease;
}
.submit a:hover{
    background:#FFCCCC;
    color:#FF0000;
    margin-left:0px;
    margin-top:0px;
    box-shadow:none;
}

JavaScript

  // DOMを変数に格納
  const selectItem = document.querySelectorAll('.select__item');
  const cartBtn = document.querySelector('.cart-btn');
  const productPhoto = document.querySelector('.product-photo');

  // 初期値として最初の色を自動選択
  selectItem[0].classList.add('checked');
  changeItem(selectItem[0]);

  // 色を選択(クリック)
  selectItem.forEach(item => {
    item.addEventListener('click', function() {
      changeItem(item); // formに選択した色の値を格納
      changeCss(item); // 選択した色のデザインを変更
      changePhoto(item); // 商品画像の写真を選択した色に差し替える
    });
  });
  // カートに入れるボタンをクリック
  cartBtn.addEventListener('click', (e) => {
    e.preventDefault();
    let skucode = document['cart-example']['sku-code'].value;
    let selectnum = document['cart-example']['select-num'].value;

    alert(`選択された商品は${skucode},数量は${selectnum}です。`)
  });

  // formに選択した色の値を格納
  function changeItem(item) {
      document['cart-example']['sku-code'].value = item.querySelectorAll("input")[0].defaultValue;
  }
  // 商品画像の写真を選択した色に差し替える
  function changePhoto(item) {
    let imgPath = item.querySelectorAll("input")[0].id;
    productPhoto.src= `https://m-kenomemo.com/sample/select-cart/images/${imgPath}.jpg`
  }
  // 選択した色のデザインを変更
  function changeCss(elm) {
    const parent = elm.parentNode;
    const child_nodes_count = parent.childElementCount;
    for(var i=0; i<child_nodes_count; i++) {
        const item = parent.children[i];
        item.classList.remove("checked");
    }
    elm.classList.add("checked");
}

選択したSKU情報を送信するフォームに渡す

長いコードなのでポイントだけメモしておきます。
今回の最終目的は、選択した商品のSKUと個数の情報を持ったフォームformcart-exampleをサーバーに送信することです。

個数はselectタグで取得できるので、選択したSKU情報をどうやって取得するかがポイントです。
選択したSKU情報を格納するsku-codeタグを準備しておきます。

<form action="" method="post" class="cart-example" name="cart-example">
  <select name="select-num" class="selectnum"> // 個数
    <option value="1">1</option>
    <option value="2">2</option>
    <option value="3">3</option>
    <option value="4">4</option>
    <option value="5">5</option>
  </select>
  <input name="sku-code" type="hidden" value=""> //選択した色を格納する
  <div class="submit">
    <a href="#" class="cart-btn">カートに入れる</a>
  </div>
</form>

色をクリックしたら送信フォームにSKU情報を格納する

選択する色の要素にはSKUの情報をもったタグを含ませます。
ここではinputタグのvalue値に色とSKU情報を持たせました。タグ自体はhidden属性なのでブラウザには表示されません。

<!-- HTML -->
<div class="select__txt">
  <p>イエロー</p>
  <input id="item_yellow" name="item2" type="hidden" value="イエロー(SKU03)">
</div>

色をクリックした時に実行されるJavascriptがこちらです。

  // formに選択した色の値を格納
  function changeItem(item) {
      document['cart-example']['sku-code'].value = item.querySelectorAll("input")[0].defaultValue;
  }

itemはクリックした要素自体を表します。その中にあるSKU情報を持つinputタグを探してformcart-examplesku-codeのvalue値に格納します。
クリックされるごとに関数が実行されて、value値も更新されていきます。

これでsubmitボタンをクリックした時に、選択した商品のSKUと個数の情報をサーバーに送信することができます。

選択した色のボタンのデザインを変更する

選択している色のボタンだけにCSSのクラスを付与すれば、他のボタンと違ったデザインを反映できます。今回は複数選択不可なラジオボタンのような動きをします。CSSでも実装できそうですが、今回はJavascriptで制御します。

// 選択した色のデザインを変更
function changeCss(elm) {
  const parent = elm.parentNode;
  const child_nodes_count = parent.childElementCount;
  for(var i=0; i<child_nodes_count; i++) {
      const item = parent.children[i];
      item.classList.remove("checked");
  }
  elm.classList.add("checked");
}

同じ兄弟要素のクラスを全て削除してから、選択した要素だけにクラスを追加しています。

まとめ

最初、色やサイズをどうやってボタンで表現するか分からなかったのですが、送信するフォームに値を渡してやれば色々な機能をもったボタンが実装できそうです。

今回は色だけですが、サイズと組み合わせるとより分かりやすいカートボタンになりそうです。