← 動的パーツギャラリーへ戻る

パーツ番号 11-10-11 ・ JavaScript使用

プルトゥリフレッシュ失敗時のリトライ表示

引っ張って更新すると1回目は通信エラーを再現し、×アイコンと再試行ボタンが表示されます。再試行を押すと通常どおり更新完了する演出です。WordPressの在庫一覧などエラー時の見た目確認に使えます。

clickdragjs

プレビュー

ソースコード

HTML

<div class="d11-10-11-wrap">
          <div class="d11-10-11-frame">
            <div class="d11-10-11-indicator" id="d1793Ind">
              <span class="d11-10-11-spin"></span>
              <span class="d11-10-11-err" id="d1793Err">
                <svg viewBox="0 0 24 24" width="15" height="15"><circle cx="12" cy="12" r="11" fill="#dc2626"/><line x1="8" y1="8" x2="16" y2="16" stroke="#fff" stroke-width="2"/><line x1="16" y1="8" x2="8" y2="16" stroke="#fff" stroke-width="2"/></svg>
                <span class="d11-10-11-retry" id="d1793Retry">再試行</span>
              </span>
            </div>
            <div class="d11-10-11-scroll" id="d1793Scroll">
              <ul class="d11-10-11-list" id="d1793List">
                <li>在庫: Aサイズ 残り2点</li>
                <li>在庫: Bサイズ 残り5点</li>
                <li>在庫: Cサイズ 残りわずか</li>
                <li>在庫: Dサイズ 在庫あり</li>
              </ul>
            </div>
          </div>
          <p class="d11-10-11-hint">▼ 下へドラッグ(1回目は更新失敗を再現)</p>
        </div>

CSS

.prev.d11-10-11{align-items:flex-start;justify-content:flex-start}
        .d11-10-11-wrap{width:100%}
        .d11-10-11-frame{position:relative;height:196px;overflow:hidden;border-radius:12px;background:#f8f9fb;border:1px solid #eef0f3}
        .d11-10-11-indicator{position:absolute;top:0;left:0;right:0;height:44px;display:flex;align-items:center;justify-content:center;opacity:0;z-index:1}
        .d11-10-11-spin{width:20px;height:20px;border:3px solid #c7d2fe;border-top-color:#4f46e5;border-radius:50%;display:block}
        .d11-10-11-indicator.is-loading .d11-10-11-spin{display:block;animation:d1793Spin .7s linear infinite}
        .d11-10-11-err{display:none;align-items:center;gap:8px}
        .d11-10-11-indicator.is-error .d11-10-11-spin{display:none}
        .d11-10-11-indicator.is-error .d11-10-11-err{display:flex}
        @keyframes d1793Spin{to{transform:rotate(360deg)}}
        .d11-10-11-retry{font-size:11px;font-weight:700;color:#dc2626;border:1px solid #fecaca;background:#fef2f2;border-radius:99px;padding:4px 12px;cursor:pointer}
        .d11-10-11-scroll{position:relative;z-index:2;height:100%;overflow-y:auto;background:#f8f9fb}
        .d11-10-11-list{list-style:none;margin:0;padding:8px;display:flex;flex-direction:column;gap:7px}
        .d11-10-11-list li{background:#fff;border:1px solid #eef0f3;border-radius:8px;padding:9px 11px;font-size:11.5px;color:#374151}
        .d11-10-11-hint{margin:8px 0 0;font-size:10.5px;color:#9ca3af;text-align:center}

JavaScript

(function(){
        var root=document.currentScript.closest('.item').querySelector('.d11-10-11');
        var scroll=root.querySelector('#d1793Scroll');
        var list=root.querySelector('#d1793List');
        var ind=root.querySelector('#d1793Ind');
        var retryBtn=root.querySelector('#d1793Retry');
        var startY=0,curD=0,dragging=false,pulling=false,refreshing=false,failNext=true;
        var MAX=64,THRESH=48;
        function getY(e){return e.touches?e.touches[0].clientY:e.clientY;}
        function setD(d){
          curD=d;
          list.style.transform='translateY('+d+'px)';
          ind.style.opacity=String(Math.min(d/THRESH,1));
        }
        function start(e){
          if(refreshing||scroll.scrollTop>0)return;
          startY=getY(e);dragging=true;pulling=false;
        }
        function move(e){
          if(!dragging||refreshing)return;
          var dy=getY(e)-startY;
          if(dy<=0)return;
          pulling=true;
          if(e.cancelable)e.preventDefault();
          setD(Math.min(dy*0.5,MAX));
        }
        function end(){
          if(!dragging)return;
          dragging=false;
          if(!pulling)return;
          pulling=false;
          if(curD>=THRESH)refresh();else reset();
        }
        function showSuccess(){
          var li=document.createElement('li');
          li.textContent='在庫: 最新状態に更新しました';
          list.insertBefore(li,list.firstChild);
          ind.classList.remove('is-loading','is-error');
          reset();
          refreshing=false;
          failNext=true;
        }
        function refresh(){
          refreshing=true;
          list.style.transition='transform .2s';
          setD(THRESH);
          ind.classList.remove('is-error');
          ind.classList.add('is-loading');
          setTimeout(function(){
            if(failNext){
              failNext=false;
              ind.classList.remove('is-loading');
              ind.classList.add('is-error');
              ind.style.opacity='1';
              list.style.transition='transform .25s ease';
              list.style.transform='translateY(0px)';
              curD=0;
              setTimeout(function(){list.style.transition='';},250);
            } else {
              showSuccess();
            }
          },850);
        }
        function reset(){
          list.style.transition='transform .25s ease';
          setD(0);
          setTimeout(function(){list.style.transition='';},250);
        }
        retryBtn.addEventListener('click',function(){
          ind.classList.remove('is-error');
          ind.classList.add('is-loading');
          ind.style.opacity='1';
          setTimeout(showSuccess,700);
        });
        scroll.addEventListener('touchstart',start,{passive:true});
        scroll.addEventListener('touchmove',move,{passive:false});
        scroll.addEventListener('touchend',end);
        scroll.addEventListener('touchcancel',end);
        scroll.addEventListener('mousedown',start);
        scroll.addEventListener('mousemove',move);
        window.addEventListener('mouseup',end);
      })();

クラス名 .d11-10-11 は本ライブラリ内での識別用です。複数のパーツを同じページで使う場合は、重複しないよう名前を変更してください。