35107


お世話になります。

jQueryで、画面幅によって処理分けしているヘッダーメニューがあります。  
1025px以上でドロップダウン型、かつスクロール時に縮小版ヘッダーに切り替えます。  
1024px以下でドロワー型、縮小版ヘッダーを表示します。

コードは以下の通りです。

[code]

    if ($(window).width() > 1024) {
        $('#main_header_nav li, #lower_header_nav li').hover(function() {
            $('>ul', this).stop(true, true).slideDown('fast');
        }, function() {
            $('>ul', this).stop(true, true).slideUp('fast');
        });
    }
    
    $('#sp_menu_open, #sp_menu_close').click(function() {
        $('#lower_header_nav').animate({ width: 'toggle' });
    });
    
    if ($(window).width() > 1024) {
        changeHeader();
        $(window).scroll(changeHeader);
    }
    
    function changeHeader() {
        var headerChangeHeight = 200; // 切り替わるタイミング
        if ($(window).scrollTop() >= headerChangeHeight) {
            $('#lower_header').slideDown('fast');
        } else {
            $('#lower_header').slideUp('fast');
        }
    }
    
[/code]

上のコードだと、画面をリサイズして1024pxをまたいだときに動かなくなってしまうので、以下のように書きかえました。

[code]

    dropDownMenu();
    $(window).resize(dropDownMenu);
    
    function dropDownMenu() {
        if ($(window).width() > 1024) {
            $('#main_header_nav li, #lower_header_nav li').hover(function() {
                $('>ul', this).stop(true, true).slideDown('fast');
            }, function() {
                $('>ul', this).stop(true, true).slideUp('fast');
            });
        }
    }
    
    drawerMenu();
    $(window).resize(drawerMenu);
    
    function drawerMenu() {
        if ($(window).width() <= 1024) {
            $('#sp_menu_open, #sp_menu_close').click(function() {
                $('#lower_header_nav').animate({ width: 'toggle' });
            });
        }
    }
    
    changeHeader();
    $(window).scroll(changeHeader);
    
    function changeHeader() {
        if ($(window).width() > 1024) {
            var headerChangeHeight = 200; // 切り替わるタイミング
            if ($(window).scrollTop() >= headerChangeHeight) {
                $('#lower_header').slideDown('fast');
            } else {
                $('#lower_header').slideUp('fast');
            }
        }
    }
    
[/code]

ですが、思うような結果が得られません。

1024px以下にリサイズすると、#lower_headerが表示されません。  
1025px以上にリサイズすると、#lower_header_navが表示されません。  
また、発生条件がわからないのですが、1024px以下にリサイズした後、ドロップダウンしてしまうことがあります。  
また同様に、1025px以上にリサイズした後、ドロップダウンが一瞬になる（スライドしない）ことがあります。

調べたり試したり、思いつく限りのことをしましたが、自力では解決できませんでした。  
だいぶ混乱しており恐縮ですが、どなたかご教示いただければ幸いです。  
よろしくお願いいたします。


CC BY-SA 3.0
2017-05-11T09:13:03.733
34638
2017-05-29T06:54:56.930
2017-05-11T09:16:23.317
23087
23087
question
javascript jquery
画面幅によって処理分けしているヘッダーメニューを画面リサイズ後も動作させたい


$.click など関数は、実行するたびにイベントハンドラが追加されます

[code]

    $('#add_event').click(function() {
      $('#trigger_event').click(function() {
        $('#result').append($('<div>hello world</div>'));
      });
    });
[/code]

[code]

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <button id="add_event">add event</button>
    <button id="trigger_event">add text</button>
    <div id="result"></div>
[/code]

左のボタンを押すたびに、右のボタンにイベントハンドラが追加されてしまう例です

@nilgiri さんの書き換え後のコードも同様の事象が起こっていて、ウィンドウがリサイズされるたびに hover イベントや click
イベントが追加されてしまっています  
そのため、一度のクリックで同じ処理がなんども行われてしまいます

これを回避するためには、ちょうど1回だけイベントハンドラが登録されているようにする必要があります

[code]

    function dropDownMenuHoverIn() {
      $('>ul', this).stop(true, true).slideDown('fast');
    }
    function dropDownMenuHoverOut() {
      $('>ul', this).stop(true, true).slideUp('fast');
    }
    var dropDownMenu = {
      on: function dropDownMenuOn() {
        $('#main_header_nav li, #lower_header_nav li').hover(
          dropDownMenuHoverIn,
          dropDownMenuHoverOut
        );
      },
      off: function dropDownMenuOff() {
        $('#main_header_nav li, #lower_header_nav li')
          .off('mouseenter', dropDownMenuHoverIn)
          .off('mouseleave', dropDownMenuHoverOut);
      },
      toggleEvent: function() {
        if ($(window).width() > 1024) {
          if (!this.bound) {
            this.on();
            this.bound = true;
          }
        } else {
          this.off();
          this.bound = false;
        }
      },
      bound: false
    };
    var drawerMenu = {
      on: function drawerMenu() {
        $('#sp_menu_open, #sp_menu_close').on('click', function() {
          $('#lower_header_nav').animate({ width: 'toggle' });
        });
      },
      off: function() {
        $('#sp_menu_open, #sp_menu_close').off('click');
      },
      toggleEvent: function() {
        if ($(window).width() > 1024) {
          this.off();
          this.bound = false;
        } else {
          if (!this.bound) {
            this.on();
            this.bound = true;
          }
        }
      },
      bound: false
    };
    
    dropDownMenu.toggleEvent();
    drawerMenu.toggleEvent();
    $(window).resize(dropDownMenu.toggleEvent());
    $(window).resize(drawerMenu.toggleEvent());
    
[/code]

イベントハンドラを登録するとき、登録済なら登録しない、という分岐を追加することで、ちょうど1回だけイベントハンドラが追加されているようにしています  
ウィンドウがリサイズされるたび、イベントハンドラが不要なら削除、イベントハンドラが必要なら登録、という処理も追加しています

`resize` `scroll` `mousemove` といったイベントは大量に発火します  
発火するたびにイベントハンドラを実行していたのでは、高負荷でブラウザが重くなってしまう可能性が高いです  
これを防ぐためには throttle(処理の間引き) をする必要があります

throttle 機能は自作もできますし、jQueryプラグインとして利用することもできます  
<https://github.com/cowboy/jquery-throttle-debounce/>


CC BY-SA 3.0
2017-05-12T11:45:26.400
34663
2017-05-12T11:45:26.400
9796
34638
answer


質問者です。  
下記のコードで解決しました！

[code]

    // メニューにホバーしたとき
    $('#main_header_nav li, #lower_header_nav li').hover(function() {
        // カーソルがのったら開く
        $('>ul', this).stop(true, true).slideDown('fast');
    }, function() {
        // カーソルが外れたら、PCでは閉じ、タブレット・スマホでは開いたまま
        if ($(window).width() > 1024) {
            $('>ul', this).stop(true, true).slideUp('fast');
        } else {
            $('>ul', this).stop(true, true).slideDown(0);
        }
    });
    
    // ハンバーガーボタン・閉じるボタンをタップしたとき
    $('#sp_menu_open, #sp_menu_close').click(function() {
        $('#lower_header_nav').animate({ width: 'toggle' });
    });
    
    // 画面をリサイズしたとき
    $(window).resize(function() {
        showLowerHeader(); // #lower_header の表示/非表示
        if ($(window).width() > 1024) {
            // PCの場合
            // #lower_header_nav を表示
            $('#lower_header_nav').animate({ width: 'show' }, 0);
            // 上記だけだと display:block になってしまうので、本来の値を再指定
            $('#lower_header_nav').css({ display: 'inline-block' });
            // 子メニューを非表示 （ホバーで表示）
            $('#main_header_nav li > ul, #lower_header_nav li > ul').slideUp(0);
        } else {
            // タブレット・スマホの場合
            // #lower_header_nav を非表示 （ハンバーガーボタンタップで表示）
            $('#lower_header_nav').animate({ width: 'hide' }, 0);
            // 子メニューを表示
            $('#main_header_nav li > ul, #lower_header_nav li > ul').slideDown(0);
        }
    });
    
    // ページを読み込んだとき
    showLowerHeader();
    
    // スクロールしたとき
    $(window).scroll(showLowerHeader);
    
    // PCのみ、スクロール量に応じて #lower_header を表示/非表示
    function showLowerHeader() {
        if ($(window).width() > 1024) {
            var h = 200; // 切り替わるタイミング
            if ($(window).scrollTop() >= h) {
                $('#lower_header').slideDown('fast');
            } else {
                $('#lower_header').slideUp('fast');
            }
        } else {
            $('#lower_header').slideDown(0);
        }
    }
    
[/code]

ポイントは下記の三点でした。

  1. イベントハンドラを1回だけ登録するよう記述する（resizeの中にclickやhoverを書かない）
  2. ifの分岐は各イベントハンドラ内に記述する
  3. 操作対象の要素を条件ごとに確実に表示・非表示する

今のところ問題なさそうですが、処理が重くなった場合、必要に応じてthrottleを行おうと思います。  
回答くださったgochoさん、本当にありがとうございます。


CC BY-SA 3.0
2017-05-29T06:54:56.930
35107
2017-05-29T06:54:56.930
23087
34638
answer