jquery パララックス効果を使ってみる

2013年12月29日

パララックスをあまりよく理解できていないので練習を兼ねたメモ。

サンプル

スクロールにより背景画像を動かす

スクロールしてある位置まで来ると、背景画像を動かす単純な例。

「div1 」の位置までスクロールすると、「クラス picture」の背景画像を下方向に動かす例。

  • 背景画像は 960×800 のサイズの画像を用意
  • 画像を表示する領域「クラス picture」の高さは500px
  • background-position は「0 0」(left top)

サンプル 1

HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Pallarax Sample 1</title>
<link rel="stylesheet" href="css/style_0.css">
</head>
<body>
<body>
<div id="wrapper">
    <div id="content">
        <div id="div1">
            <div class="text_area">
                <h1>Pallarax Sample</h1>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing...</p>
            </div><!-- end .text_area -->
            <div class="picture">
                <div class="inner">
                    <h2>Sample1 </h2>
                    <p><a href="#">Ducimus deleniti minus</a></p>
                </div><!-- end .inner --> 
            </div><!-- end .picture --> 
        </div><!-- end #div1 --> 
    </div><!-- end #content --> 
</div><!-- end #wrapper --> 
</body>
</html>

CSS

#wrapper {
  position: relative;
  width: 960px;
  margin: 0 auto;
  height: 2000px;
}
#content {
  margin: 10px 0;
}
.text_area {
  position: relative;
  padding: 4em;
}
.text_area p {
  margin: 2em;
  width: 550px;
}
.picture {
  position: relative;
  width: 960px;
  height: 500px;
  overflow: hidden;
}
#div1 .picture {
  background: url(images/div1_bg.jpg) 0 0 no-repeat;
}
.inner {
  position: absolute;
  top: 30px;
  left: 30px;
}
.inner h2 {
  color: #FFF;
  padding: 50px 0 0 20px;
}
.inner p {
  padding-left: 50px;
  font-size: 20px;
  color: #FFF;
}

スクロール量が「div1 」の位置に達したら、「クラス picture」の背景画像の「background-position」を利用して速度を調整する。

jQuery

<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
jQuery(function($){  
  var div1$ = $('#div1');
  var div1_ot = div1$.offset().top;  //「div1 」の位置
  var div1pic$ =$('#div1 .picture');
  var div1pic_ot = div1pic$.offset().top;  //「クラス picture」の位置
  var div1_factor = -0.4;  //速度を調整する値
  
  $(window).scroll(function(){
    var dy = $(this).scrollTop();
     
    if(dy > div1_ot) {
        div1pic$.css('background-position',  '0 ' + (dy-div1_ot) * div1_factor + 'px');  
    }else{
        div1pic$.css('background-position', '0 0');
    }          
  });
    
});
</script>

背景画像の領域の要素を固定する

背景画像の領域(class=”picture”)にある div 要素(class=”inner”)を、スクロールした際に背景画像の領域の下辺に来るまで固定して表示する場合。

サンプル 2

HTML と CSS は同じ。

  • スクロール量が背景画像の領域(class=”picture”)に達したら、 div 要素(class=”inner”)を「fixed」に変更。
  • 但し、「fixed」にするとブラウザの幅により表示位置が異なるので以下を実行
  • ウィンドウの幅「$(window).width()」と div 要素(’#wrapper’)の幅「$(‘#wrapper’).width()」を取得
  • 差分の半分を算出して、左側のオフセットとして指定
  • 値が負の場合はオフセットは0に
  • スクロール量が背景画像の領域(class=”picture”)を超えたら「absolute」に戻す
  • この時 div 要素(class=”inner”)の高さを考慮する

jQuery

jQuery(function($){
  
  var div1$ = $('#div1');
  var div1_ot = div1$.offset().top;
  var div1pic$ =$('#div1 .picture');
  var div1pic_ot = div1pic$.offset().top;
  var div1_factor = -0.4;
  
  $(window).scroll(function(){
    var dy = $(this).scrollTop();
     
    if(dy > div1_ot) {
        div1pic$.css('background-position',  '0 ' + (dy-div1_ot) * div1_factor + 'px');  
    }else{
        div1pic$.css('background-position', '0 0');
    }  
    
    if(dy > div1pic_ot && dy < div1pic_ot + div1pic$.height() -$('.inner').height()) {    
      var window_w = $(window).width();
      var wrapper_w = $('#wrapper').width();
      var offset_left = (window_w - wrapper_w) / 2;
      if(offset_left < 0) offset_left = 0;
      $('#div1 .inner').css({
        position: 'fixed',
        top : '30px',
        left: offset_left + 30 + 'px'
      });
    }else{
      $('#div1 .inner').css({
        position: 'absolute',
        top : '30px',
        left: '30px'
      });
    }    
  });  
});
&#91;/code&#93;

<h4>背景画像を固定「fixed」にする場合</h4>

<a href="http://www.webdesignleaves.com/samples/jQuery/parallax/pallarax_sample201312_2-2.html" target="_blank">サンプル 2-2</a>

HTML は同じ。

<ul>
  <li>CSS では背景画像を「fixed」に指定</li>
  <li>その際に水平位置を「50%」に指定</li>
</ul>
CSS の変更箇所は以下の一箇。
#div1 .picture { background: url(images/div1_bg.jpg) 50% 0 no-repeat fixed; }
  • jQuery でも水平方向の位置を「50%(center)」に指定。
  • 「fixed」の場合、「background-position」の値はブラウザの表示領域からの値なので、必要であれば、(dy-div1_ot)を単に「dy」にして速度を調節する値を変更するなどする。
  • 垂直方向の位置は「0(top)」に指定しているが、前後の要素により表示される位置が異なるので調整が必要なことも考えられる。

jQuery 変更箇所

if(dy > div1_ot) {
        div1pic$.css('background-position',  '50% ' + (dy-div1_ot) * div1_factor + 'px');  
    }else{
        div1pic$.css('background-position', '50% 0');
    }

jQuery

jQuery(function($){
  
  var div1$ = $('#div1');
  var div1_ot = div1$.offset().top;
  var div1pic$ =$('#div1 .picture');
  var div1pic_ot = div1pic$.offset().top;
  var div1_factor = -0.4;
  
  $(window).scroll(function(){
    var dy = $(this).scrollTop();
     
    if(dy > div1_ot) {
        div1pic$.css('background-position',  '50% ' + (dy-div1_ot) * div1_factor + 'px');  
    }else{
        div1pic$.css('background-position', '50% 0');
    }  
    
    if(dy > div1pic_ot && dy < div1pic_ot + div1pic$.height() -$('.inner').height()) {    
      var window_w = $(window).width();
      var wrapper_w = $('#wrapper').width();
      var offset_left = (window_w - wrapper_w) / 2;
      if(offset_left < 0) offset_left = 0;
        $('#div1 .inner').css({
          position: 'fixed',
          top : '30px',
          left: offset_left + 30 + 'px'
      });
    }else{
      $('#div1 .inner').css({
        position: 'absolute',
        top : '30px',
        left: '30px'
      });
    }    
  });
  
});
&#91;/code&#93;


<h4>背景画像の垂直方向の位置が「bottom」の場合</h4>

<ul>
  <li>背景画像は 960x800 のサイズの画像を用意</li>
  <li>画像を表示する領域「クラス picture」の高さは500px</li>
  <li>background-position は「0 100%」(left bottom)</li>
  <li>このサンプルの背景画像は「固定(fixed)」ではない</li>
</ul>

<a href="http://www.webdesignleaves.com/samples/jQuery/parallax/pallarax_sample201312_3.html" target="_blank">サンプル 3</a>

HTML は同じ。

CSS の変更箇所は以下の1箇所。
#div1 .picture { background: url(images/div1_bg.jpg) 0 100% no-repeat; }

スクロール量が「div1 」の位置に達したら、「クラス picture」の背景画像の「background-position」を利用して速度を調整する。

但し、background-position が「0 100%」(left bottom)なので、表示領域と画像の高さの差分(img_offset)を考慮する。

差分(img_offset)の算出は画像要素を生成してその高さを使用して取得しているが、画像の高さが一定(同じ)であれば単に(表示領域の高さ)-(画像の高さ)「500 - 800」で計算したほうが簡単。

jQuery

jQuery(function($){
  
  var div1$ = $('#div1');
  var div1_ot = div1$.offset().top;
  var div1pic$ =$('#div1 .picture');
  var div1pic_ot = div1pic$.offset().top;
  var div1pic_h = div1pic$.height(); // 背景領域の高さ
  var bg_img = new Image(); //img 要素の生成   
  var url = div1pic$.css('background-image'); //画像のパスの取得
  url = url.match(/(images\/.+\.jpg)/);   //「images/div1_bg.jpg」部分を抽出
  bg_img.src = url[0];  
  var img_height = bg_img.height; // 背景画像の高さを取得
  var img_offset = div1pic_h - img_height;  //-300 (500 - 800)
  var div1_factor = 1.01;

  $(window).scroll(function(){
    var dy = $(this).scrollTop();
     
    if(dy > div1_ot) {
      div1pic$.css('background-position', '0 ' + (img_offset + (dy - div1_ot) * div1_factor) + 'px');  
    }else{
        div1pic$.css('background-position', '0 100%');
    }
      
    // div 要素(class=”inner”)を固定表示(前述と同じ)
    if(dy > div1pic_ot && dy < div1pic_ot + div1pic$.height() -$('.inner').height()) {    
      var window_w = $(window).width();
      var wrapper_w = $('#wrapper').width();
      var offset_left = (window_w - wrapper_w) / 2;
      if(offset_left < 0) offset_left = 0;
        $('#div1 .inner').css({
          position: 'fixed',
          top : '30px',
          left: offset_left + 30 + 'px'
      });
    }else{
      $('#div1 .inner').css({
        position: 'absolute',
        top : '30px',
        left: '30px'
      });
    }    
  });
  
});
&#91;/code&#93;

<h3>スクロールにより画像要素を動かす</h3>
背景画像ではなく、代わりに画像要素を動かす場合。

<a href="http://www.webdesignleaves.com/samples/jQuery/parallax/pallarax_sample201312_4.html" target="_blank">サンプル 4</a>

サンプル2を元に変更して作成。

HTML: 背景画像の代わりに、画像要素を配置。

<div class="picture"> <img src="images/div1_bg.jpg" height="800" width="960" alt=""><!-- 変更(追加) --> <div class="inner"> <h2>Sample 4 </h2> <p><a href="#">Ducimus deleniti minus</a></p> </div><!-- end .inner --> </div><!-- end .picture -->

CSS:背景画像の指定を削除して、配置した画像を絶対配置に変更

/* 削除 #div1 .picture { 
  background: url(images/div1_bg.jpg) 0 0 no-repeat;
}
*/
/* 変更(追加) */
#div1 .picture img {
  position: absolute;
  top: 0;
  left: 0;
}

jQuery:画像要素の CSS の「top」の値を変化させる

jQuery(function($){
  
  var div1$ = $('#div1');
  var div1_ot = div1$.offset().top;
  var div1pic$ =$('#div1 .picture');
  var div1pic_img$ =$('#div1 .picture img');  //追加
  var div1pic_ot = div1pic$.offset().top;
  var div1_factor = -0.4;
  
  $(window).scroll(function(){
    
    var dy = $(this).scrollTop();
    if(dy > div1_ot) {
        div1pic_img$.css('top', (dy-div1_ot) * div1_factor + 'px');    //変更
    }else{
        div1pic_img$.css('top', '0');  //変更
    }  
    
    if(dy > div1pic_ot && dy < div1pic_ot + div1pic$.height() -$('.inner').height()) {    
      var window_w = $(window).width();
      var wrapper_w = $('#wrapper').width();
      var offset_left = (window_w - wrapper_w) / 2;
      if(offset_left < 0) offset_left = 0;
        $('#div1 .inner').css({
          position: 'fixed',
          top : '30px',
          left: offset_left + 30 + 'px'
      });
    }else{
      $('#div1 .inner').css({
        position: 'absolute',
        top : '30px',
        left: '30px'
      });
    }    
  });
  
});
&#91;/code&#93;

<h3>複数の背景画像にパララックス効果を使う場合</h3>

<a href="http://www.webdesignleaves.com/samples/jQuery/parallax/pallarax_sample201312_5.html" target="_blank">サンプル 5</a>

サンプル2を元に変更して作成。

基本的には同じパターンの繰り返しなので前述と大差はないが jQuery の記述では何故か原因はわからないが、記述の順番で機能しない場合がある。また jQuery の記述を短くするため「.each()」を利用する方法も試してみる。

HTML
<body> <div id="wrapper"> <div id="content"> <div id="div1" class="pallarax_parent"> <div class="text_area"> <h1>Pallarax Sample</h1> <p>Lorem ipsum dolor sit amet...</p> </div> <div class="picture"> <div class="inner"> <h2>Sample 5-1 </h2> <p><a href="#">Ducimus deleniti minus</a></p> </div> </div> </div> <div id="div2" class="pallarax_parent"> <div class="text_area"> <h1>Pallarax Sample</h1> <p>Lorem ipsum dolor sit amet...</p> </div> <div class="picture"> <div class="inner"> <h2>Sample 5-2 </h2> <p><a href="#">Ducimus deleniti minus</a></p> </div> </div> </div> <div id="div3" class="pallarax_parent"> <div class="text_area"> <h1>Pallarax Sample</h1> <p>Lorem ipsum dolor sit amet...</p> </div> <div class="picture"> <div class="inner"> <h2>Sample 5-3 </h2> <p><a href="#">Ducimus deleniti minus</a></p> </div> </div> </div> </div> <!-- end #content --> </div> <!-- end #wrapper --> </body>

css は「#wrapper」の高さの変更と「#div2 .picture」「#div3 .picture」の追加。

CSS

#wrapper {
  position: relative;
  width: 960px;
  margin: 0 auto;
  padding-bottom: 60px;
  height: 3500px;
}
#content {
  margin: 10px 0;
}
.text_area {
  position: relative;
  padding: 4em;
}
.text_area p {
  margin: 2em;
  width: 550px;
}
.picture {
  position: relative;
  width: 960px;
  height: 500px;
  overflow: hidden;
}
#div1 .picture {
  background: url(images/div1_bg.jpg) 0 0 no-repeat;
}
#div2 .picture {
  background: url(images/div2_bg.jpg) 0 0 no-repeat;
}
#div3 .picture {
  background: url(images/div3_bg.jpg) 0 0 no-repeat;
}
.inner {
  position: absolute;
  top: 30px;
  left: 30px;
}
.inner h2 {
  color: #FFF;
  padding: 50px 0 0 20px;
}
.inner p {
  padding-left: 50px;
  font-size: 20px;
  color: #FFF;
}

jQuery もほぼ同じパターンの繰り返し。

但し、if(dy > div3_ot) の部分の条件分岐は何故か最後(div3_ot)から記述しないと最初のパララックス効果が効かない。

jQuery

var div1$ = $('#div1');
  var div1_ot = div1$.offset().top;
  var div1pic$ =$('#div1 .picture');
  var div1pic_ot = div1pic$.offset().top;
  var div1_factor = -0.4;
  
  var div2$ = $('#div2');
  var div2_ot = div2$.offset().top;
  var div2pic$ =$('#div2 .picture');
  var div2pic_ot = div2pic$.offset().top;
  var div2_factor = -0.4;
  
  var div3$ = $('#div3');
  var div3_ot = div3$.offset().top;
  var div3pic$ =$('#div3 .picture');
  var div3pic_ot = div3pic$.offset().top;
  var div3_factor = -0.4;
  
  $(window).scroll(function(){
    var dy = $(this).scrollTop();    
    
    if(dy > div3_ot) {
        div3pic$.css('background-position',  '0 ' + (dy-div3_ot) * div3_factor + 'px');  
    }else{
        div3pic$.css('background-position', '0 0');
    }  
    
    if(dy > div2_ot) {
        div2pic$.css('background-position',  '0 ' + (dy-div2_ot) * div2_factor + 'px');  
    }else{
        div1pic$.css('background-position', '0 0');
    }
    
    if(dy > div1_ot) {
        div1pic$.css('background-position',  '0 ' + (dy-div1_ot) * div1_factor + 'px');  
      console.log(dy-div1_ot);
    }else{
        div1pic$.css('background-position', '0 0');
    }  
    
    
    
    if(dy > div1pic_ot && dy < div1pic_ot + div1pic$.height() -$('.inner').height()) {    
      var window_w = $(window).width();
      var wrapper_w = $('#wrapper').width();
      var offset_left = (window_w - wrapper_w) / 2;
      if(offset_left < 0) offset_left = 0;
        $('#div1 .inner').css({
          position: 'fixed',
          top : '30px',
          left: offset_left + 30 + 'px'
      });
    }else{
      $('#div1 .inner').css({
        position: 'absolute',
        top : '30px',
        left: '30px'
      });
    }
    
    if(dy > div2pic_ot && dy < div2pic_ot + div2pic$.height() -$('.inner').height()) {    
      var window_w = $(window).width();
      var wrapper_w = $('#wrapper').width();
      var offset_left = (window_w - wrapper_w) / 2;
      if(offset_left < 0) offset_left = 0;
        $('#div2 .inner').css({
          position: 'fixed',
          top : '30px',
          left: offset_left + 30 + 'px'
      });
    }else{
      $('#div2 .inner').css({
        position: 'absolute',
        top : '30px',
        left: '30px'
      });
    }    
    
    if(dy > div3pic_ot && dy < div3pic_ot + div3pic$.height() -$('.inner').height()) {    
      var window_w = $(window).width();
      var wrapper_w = $('#wrapper').width();
      var offset_left = (window_w - wrapper_w) / 2;
      if(offset_left < 0) offset_left = 0;
        $('#div3 .inner').css({
          position: 'fixed',
          top : '30px',
          left: offset_left + 30 + 'px'
      });
    }else{
      $('#div3 .inner').css({
        position: 'absolute',
        top : '30px',
        left: '30px'
      });
    }        
  });
&#91;/code&#93;

<h4>.each() の利用</h4>

前述の jQuery の記述では繰り返し同じパターンを記述しているので、以下のように.each() を利用すると簡潔に記述できる。

jQuery(function($){ var window$ = $(window); $('.picture').each(function(index) { var this$ = $(this); var positions = this$.offset(); var parent$ = this$.closest('.pallarax_parent'); var parentPositions = parent$.offset(); var inner$ = this$.find('.inner'); window$.scroll(function() { var window_st = window$.scrollTop(); if(window_st > parentPositions.top) { this$.css('background-position', '0 ' + (window_st - parentPositions.top) * (-0.4) + 'px'); }else{ this$.css('background-position', '0 0'); } if(window_st > positions.top && window_st < positions.top + this$.height() - inner$.height()) { var window_w = $(window).width(); var wrapper_w = $('#wrapper').width(); var offset_left = (window_w - wrapper_w) / 2; if(offset_left < 0) offset_left = 0; inner$.css({ position: 'fixed', top : '30px', left: offset_left + 30 + 'px' }); }else{ inner$.css({ position: 'absolute', top : '30px', left: '30px' }); } }); }); }); &#91;/code&#93; また、上記では速度を調節する値(-0.4)をそのまま記述しているが、HTML5 のデータ属性( data-speed="-0.4")を使って指定すればそれぞれ別の値を指定可能。 &#91;code&#93; <div class="picture" data-speed="-0.4" > <div class="inner"> <h2>Sample 5-1 </h2> <p><a href="#">Ducimus deleniti minus</a></p> </div> </div>

jQuery の速度を調節する値「-0.4」を 「this$.data('speed')」に変更。

if(window_st > parentPositions.top) {
        this$.css('background-position',  '0 ' + (window_st - parentPositions.top) * this$.data('speed') + 'px');  
      }else{
        this$.css('background-position', '0 0');
      }

リンクをクリックすると背景画像を拡大表示する

パララックスとは関係ないが、リンクをクリックするとその背景画像をブラウザいっぱいに拡大表示するようにしてみる。

サンプル 6

次のような拡大表示するためのリンク要素と閉じるためのリンク要素を HTML に追加。

<p><a href="#" class="expand">Click</a></p>
<p><a href="#" class="close">Close</a></p>

HTML

<body>
<div id="wrapper">
    <div id="content">
        <div id="div1" class="pallarax_parent">
            <div class="text_area">
                <h1>Pallarax Sample</h1>
                <p>Lorem ipsum dolor sit amet...</p>
            </div>
            <div class="picture"  data-speed="-0.3" >
                <div class="inner">
                    <h2>Sample 6-1 </h2>
                    <p><a href="#" class="expand">Click</a></p>
                    <p><a href="#" class="close">Close</a></p>
                </div>
            </div>
        </div>
        
        <div id="div2" class="pallarax_parent">
            <div class="text_area">
                <h1>Pallarax Sample</h1>
                <p>Lorem ipsum dolor sit amet...</p>
            </div>
            <div class="picture"  data-speed="-0.4" >
                <div class="inner">
                    <h2>Sample 6-2 </h2>
                    <p><a href="#" class="expand">Click</a></p>
                    <p><a href="#" class="close">Close</a></p>
                </div>
            </div>
        </div>
        
        <div id="div3" class="pallarax_parent">
            <div class="text_area">
                <h1>Pallarax Sample</h1>
                <p>Lorem ipsum dolor sit amet...</p>
            </div>
            <div class="picture"  data-speed="-0.45" >
                <div class="inner">
                    <h2>Sample 6-3 </h2>
                    <p><a href="#" class="expand">Click</a></p>
                    <p><a href="#" class="close">Close</a></p>
                </div>
            </div>
        </div>
        
    </div>
    <!-- end #content --> 
</div>
<!-- end #wrapper --> 
</body>

閉じるためのリンク要素は最初は非表示にしておく。
また、アニメーションに使う部品(.whitepanel)を用意。

CSS

a.close {
  display: none;
}

.whitepanel {
  height: 200px;
  width: 100%;
  background-color: #FFF;
  display: none;
  position: fixed;
  z-index: 200;
}
#whitepanel1 {
  top: 0;
}
#whitepanel2 {
  bottom: 0;
}

jQuery では以下のような変数を追加。

//拡大表示した際はスクロールを無効にするためのフラグ
var scroll_flag = true;

//拡大表示する際に、背景画像の「background-position」を変更するため、元の値を格納する変数
var backgroundPosition_data;

//拡大表示する際の window のスクロールトップの値
//拡大表示した後にスクロールしても、元の位置に戻るための値
var scroll_top;

jQuery

jQuery(function($){
  
  var window$ = $(window);
  var scroll_flag = true;
  $('.picture').each(function(index) {
    var this$ = $(this);
    var positions = this$.offset();
    var parent$ = this$.closest('.pallarax_parent');
    var parentPositions = parent$.offset();
    var inner$ = this$.find('.inner');
    
    window$.scroll(function() {
      //拡大表示していなければ(内容は同じ)
      if(scroll_flag){
        var window_st = window$.scrollTop();
        if(window_st > parentPositions.top) {
this$.css('background-position',  '0 ' + (window_st-parentPositions.top) * this$.data('speed') + 'px');  
        }else{
          this$.css('background-position', '0 0');
        }
        
        if(window_st > positions.top && window_st < positions.top + this$.height() -inner$.height()) {    
          var window_w = $(window).width();
          var wrapper_w = $('#wrapper').width();
          var offset_left = (window_w - wrapper_w) / 2;
          if(offset_left < 0) offset_left = 0;
          inner$.css({
            position: 'fixed',
            top : '30px',
            left: offset_left + 30 + 'px'
          });
        }else{
          inner$.css({
            position: 'absolute',
            top : '30px',
            left: '30px'
          });
        }    
      }
      
    });
  });
  
  var backgroundPosition_data;
  var scroll_top;
  $('a.expand').click(function(){
    //拡大表示した際はスクロールを無効にする
    scroll_flag = false;
    //現在のスクロールの値を取得して代入
    scroll_top = window$.scrollTop();
    var window_height = window$.height();
    var window_width = window$.width();
    var div_picture$ = $(this).closest('.picture');
    //現在の background-position の値を取得して代入
    backgroundPosition_data = div_picture$.css('background-position');
    //アニメーション用の要素を追加
    $('body').append('<div class="whitepanel" id="whitepanel1"></div>');
    $('body').append('<div class="whitepanel" id="whitepanel2"></div>');
    //アニメーション用の要素を表示
    $('.whitepanel').show(100, function(){
        div_picture$.css({
        backgroundSize:'cover',
        top: 0,
        left: 0,
        position: 'fixed',
        //中央に配置
        backgroundPosition: '50% 50%',
        height: window_height,
        width: window_width,      
        zIndex: 100
      });
      
      $('a.expand').hide(50, function(){
        $('a.close').show(50, function(){
          //アニメーション用の要素をスライドアップして非表示に
          $('.whitepanel').slideUp(500, function(){
            //アニメーション用の要素を削除
            $('.whitepanel').remove();
          });
        });
      });
    });
      
    return false;
  });
  
  $('a.close').click(function(){
    //スクロールを有効にする
    scroll_flag = true;
    //拡大表示した時点でのスクロール値に戻す
    window$.scrollTop(scroll_top);
    var div_picture$ = $(this).closest('.picture');
    $('body').append('<div class="whitepanel" id="whitepanel1"></div>');
    $('body').append('<div class="whitepanel" id="whitepanel2"></div>');
    $('.whitepanel').slideDown(300, function(){
      //拡大表示した時点の設定に戻す
      div_picture$.css({
        backgroundSize:'auto',
        position: 'relative',
        backgroundPosition: backgroundPosition_data,
        height: '500px',
        width: '960px',      
        zIndex: 0
      });
      
      $('a.expand').show(50, function(){
        $('a.close').hide(50, function(){
          $('.whitepanel').slideUp(500, function(){
            $('.whitepanel').remove();
          });
        });
      });
    });
    
    return false;
  });  
});

スクロールの動きに合わせてナビゲーションの表示を変更

サンプル 7

ナビゲーションを設置して、スクロール量によりその背景色を変更する。

ナビゲーションの HTML
ナビゲーションの他にコンテンツのトップに新しい div 要素(id="top")を追加。

<body>
<div id="wrapper">
<div id="header">
    <h1><a href="#">Pallarax Sample 7</a></h1>
    <div id="navi">
      <ul>
        <li><a id="to_home" href="#">Home</a></li>
        <li><a id="to_div1" href="#div1">div1</a></li>
        <li><a id="to_div2" href="#div2">div2</a></li>
        <li><a id="to_div3" href="#div3">div3</a></li>
      </ul>
    </div>
    <!-- end of #navi--> 
  </div>
  <!-- end of #header-->
    <div id="content">
    <div id="top">
      <h1>Pallarax Sample</h1>
        <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ducimus, deleniti, minus minima delectus beatae laudantium labore at rerum dolorum perspiciatis esse rem fuga impedit nulla eos facilis corrupti numquam quibusdam?</p>
    </div>
        <div id="div1" class="pallarax_parent">
            <div class="text_area">
                <h1>Div1</h1>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ducimus, deleniti, minus minima delectus beatae laudantium labore at rerum dolorum perspiciatis esse rem fuga impedit nulla eos facilis corrupti numquam quibusdam?</p>
            </div>
            <div class="picture"  data-speed="-0.25" >
                <div class="inner">
                    <h2>Sample 7-1 </h2>
                    <p><a href="#" class="expand">Click</a></p>
                    <p><a href="#" class="close">Close</a></p>
                </div>
            </div>
        </div>
        ・・・以下省略(前述と同じ)・・・

ナビゲーションの CSS
スクロールがその領域にある場合のクラス(selected)を作成し、背景色を設定。

#navi {
  float: right;
}
#navi ul {
  margin: 0;
  padding: 0;
}
#navi ul li {
  float : left
}
#navi ul li a {
  margin: 0;
  display : block;
  text-align: center;
  color: #666;
  padding: 10px;
}
#navi ul li a:hover, #navi ul li a.selected {
  color: #FFF;
  background: #999;
}

#top {
  height: 200px;
  margin-top: 48px;
  padding: 50px;
  color: #999;
}

以下の jQuery を追加。
それぞれの領域にスクロールした場合、ナビゲーションの a 要素にクラス「selected」を追加。

var div1_ot = $('#div1').offset().top;
var div2_ot = $('#div2').offset().top;
var div3_ot = $('#div3').offset().top;
var navi_a$ = $('#navi a');
    
window$.scroll(function() {
  var window_scrollTop = $(this).scrollTop();
  if(scroll_flag){
    if(window_scrollTop > div3_ot - 50) {
      navi_a$.removeClass('selected');
      $(navi_a$.get(3)).addClass('selected');
    }else if(window_scrollTop > div2_ot - 50){
      navi_a$.removeClass('selected');
      $(navi_a$.get(2)).addClass('selected');    
    }else if(window_scrollTop > div1_ot - 50){
      navi_a$.removeClass('selected');
      $(navi_a$.get(1)).addClass('selected');    
    }else{
      navi_a$.removeClass('selected');
    }
  }      
});

スムーズスクロールの設定

サンプル 8

  • スクロールさせることができる要素が html か body かを判定
  • ナビゲーションのリンクだけではなく、リンク先が「#」で始まるものをクリックするとスムーズスクロールするようにする
  • 除外するものは「not()」で対象から外す
  • 背景画像が拡大表示されているとき(if(!scroll_flag))は、まずクローズリンクをクリックさせた後でスムーズスクロールする。
  • その際に「クローズリンクをクリックする」処理とスムーズスクロールが同時に発生しないように「window.setTimeout()」を利用
  • 「window.setTimeout()」では$(this)が使えないのでその前に変数に格納
  • また、パララックスによる画像の位置のずれ(?)をリセットするために最後に「window.location.reload()」を実行。(原因を調べる必要あり。あまり効率的ではない。)IE ではトップに戻ってしまうのでNG。何か別の方法が必要。

関連ページ:「ページ内リンクへアニメーションで移動

var isHtmlScrollable = (function(){
  var html = $('html'), top = html.scrollTop();
  var elm = $('<div/>').height(10000).prependTo('body');
  html.scrollTop(10000);
  var rs = !!html.scrollTop();
  html.scrollTop(top);
  elm.remove();
  return rs;
})();

$('a[href^=#]').not('.inner a').click(function(){
  $('.picture').each(function(index) {
     $(this).css({
       backgroundPosition: '0 0',
       height: '500px'         
       });      
  });
  var this$ = $(this);
  var hrefval= this$.attr('href');
  var positiontop;
  var speed;
  var targetelement;
  if(!scroll_flag){      
    $('a.close').click();      
    var timer = window.setTimeout(function(){         
      if(hrefval == "#") {
        positiontop = 0;
        speed = 900 + this$.offset().top /30;    
      }else{
        targetelement = $(hrefval);
        positiontop = targetelement.offset().top - 48;  //header の高さを調整
        speed = 900 + (positiontop + 100) / 10;
      }
      $(isHtmlScrollable ? 'html' : 'body').stop().animate({
        scrollTop: positiontop
        },  speed, function(){
          //パララックスによる画像のずれをリセット(?)IE ではトップに戻ってしまう
          // window.location.reload();
        });          
      }, 1000);
  }else{
    if(hrefval == "#") {
      positiontop = 0;
      speed = 900 + this$.offset().top /30;    
    }else{
      targetelement = $(hrefval);
      positiontop = targetelement.offset().top - 48;  //header の高さを調整
      speed = 900 + (positiontop + 100) / 10;
    }
    $(isHtmlScrollable ? 'html' : 'body').stop().animate({
      scrollTop: positiontop
    },  speed, function(){
      //パララックスによる画像のずれをリセット(?)IE ではトップに戻ってしまう
      // window.location.reload();
    });      
  }        
  return false;
});

課題

パララックスを適用する背景画像の位置「offset().top」や大きさ、スクロールの速度(変化させる値)によって、場合によっては前述の方法では思うような結果が得られない場合がある。

そのような場合、それぞれの背景画像の位置やスクロール速度を(HTML5 のデータ属性を使うなどして)個々に調整することができるが、何かもう少し効率的な方法はないものか?

以下は、スクロール量の速度を変化させるタイミングの条件とスクロールの速度を変化させる値を変更してみた例。

この例の場合、パララックスを適用する領域がブラウザに表示されていればポジションを変化させる。以下はそのための判定方法。

  • ブラウザの下辺の位置の値「window_st + window_height」がその領域の上辺の位置の値「positions.top」より大きく
  • かつその領域の下辺の位置の値「positions.top + this$.height()」がブラウザの上辺の位置の値「window_st」より大きい場合

その領域の垂直位置の値を、その時点のスクロール量からその領域のオフセットを引いた値「window_st - positions.top」からある値「(window_st - positions.top)/ this$.data('speed')」を差し引いたものにする。

サンプル 9

jQuery

jQuery(function($){
  
  var window$ = $(window);
  var scroll_flag = true;
  $('.picture').each(function(index) {
    var this$ = $(this);
    var positions = this$.offset();
    var parent$ = this$.closest('.pallarax_parent');
    var parentPositions = parent$.offset();
    var inner$ = this$.find('.inner');
         
    window$.scroll(function() {
      if(scroll_flag){
        var window_st = window$.scrollTop();
        var window_height =window$.height();  //追加
        //変更箇所
        if ((window_st + window_height) > positions.top && ((positions.top + this$.height()) > window_st)) {
           var offsetY =  (window_st - positions.top) -(window_st - positions.top)/ this$.data('speed');
           //var offsetY =  (window_st - positions.top + 300) * this$.data('speed') ;
           //var offsetY = window_st * this$.data('speed');
           var bg_positions = '0 ' + offsetY + 'px';
           this$.css('backgroundPosition', bg_positions);         
         }
         //ここまで。後は同じ
                  
        if(window_st > positions.top && window_st < positions.top + this$.height() -inner$.height()) {    
          var window_w = $(window).width();
          var wrapper_w = $('#wrapper').width();
          var offset_left = (window_w - wrapper_w) / 2;
          if(offset_left < 0) offset_left = 0;
          inner$.css({
            position: 'fixed',
            top : '30px',
            left: offset_left + 30 + 'px'
          });
        }else{
          inner$.css({
            position: 'absolute',
            top : '30px',
            left: '30px'
          });
        }            
      }      
    });
  });

  var backgroundPosition_data;
  var scroll_top;
  $('a.expand').click(function(){
    scroll_flag = false;
    scroll_top = window$.scrollTop();
    var window_height = window$.height();
    var window_width = window$.width();
    var div_picture$ = $(this).closest('.picture');
    backgroundPosition_data = div_picture$.css('background-position');
    $('body').append('<div class="whitepanel" id="whitepanel1"></div>');
    $('body').append('<div class="whitepanel" id="whitepanel2"></div>');
    $('.whitepanel').show(100, function(){
        var window_w = $(window).width();
        var wrapper_w = $('#wrapper').width();
        var offset_left = (window_w - wrapper_w) / 2;
        if(offset_left < 0) offset_left = 0;
        div_picture$.css({
        backgroundSize:'cover',
        top: 48,  //変更(header の高さ分)
        left: offset_left  + 'px',
        position: 'fixed',
        backgroundPosition: '50% 50%',
        height: window_height,
        width: '960px',      
        zIndex: 100
      });
      
      $('a.expand').hide(50, function(){
        $('a.close').show(50, function(){
          $('.whitepanel').slideUp(500, function(){
            $('.whitepanel').remove();
          });
        });
      });
    });  
    return false;
  });
  
  $('a.close').click(function(){
    scroll_flag = true;
    window$.scrollTop(scroll_top);
    var div_picture$ = $(this).closest('.picture');
    $('body').append('<div class="whitepanel" id="whitepanel1"></div>');
    $('body').append('<div class="whitepanel" id="whitepanel2"></div>');
    $('.whitepanel').slideDown(300, function(){
      div_picture$.css({
        backgroundSize:'auto',
        position: 'relative',
        backgroundPosition: backgroundPosition_data,
        //backgroundPosition: '0 0',
        height: '500px',
        width: '960px',  
        //top: 48,
        left: 0  ,  
        zIndex: 0
      });
      
      $('a.expand').show(50, function(){
        $('a.close').hide(50, function(){
          $('.whitepanel').slideUp(500, function(){
            $('.whitepanel').remove();
          });
        });
      });
    });
    return false;
  });  
    
  var div1_ot = $('#div1').offset().top;
  var div2_ot = $('#div2').offset().top;
  var div3_ot = $('#div3').offset().top;
  var navi_a$ = $('#navi a');
    
  window$.scroll(function() {
    var window_scrollTop = $(this).scrollTop();
    if(scroll_flag){
      if(window_scrollTop > div3_ot - 50) {
        navi_a$.removeClass('selected');
        $(navi_a$.get(3)).addClass('selected');
      }else if(window_scrollTop > div2_ot - 50){
        navi_a$.removeClass('selected');
        $(navi_a$.get(2)).addClass('selected');    
      }else if(window_scrollTop > div1_ot - 50){
        navi_a$.removeClass('selected');
        $(navi_a$.get(1)).addClass('selected');    
      }else{
        navi_a$.removeClass('selected');
      }
    }      
  });
    
  var isHtmlScrollable = (function(){
    var html = $('html'), top = html.scrollTop();
    var elm = $('<div/>').height(10000).prependTo('body');
    html.scrollTop(10000);
    var rs = !!html.scrollTop();
    html.scrollTop(top);
    elm.remove();
    return rs;
  })();
  
  $('a[href^=#]').not('.inner a').click(function(){
    var this$ = $(this);
    var hrefval= this$.attr('href');
    var positiontop;
    var speed;
    var targetelement;
    if(!scroll_flag){      
      $('a.close').click();      
      var timer = window.setTimeout(function(){         
        if(hrefval == "#") {
          positiontop = 0;
          speed = 900 + this$.offset().top /30;    
        }else{
          targetelement = $(hrefval);
          positiontop = targetelement.offset().top - 48;  //header の高さを調整
          //positiontop = targetelement.offset().top;
          speed = 900 + (positiontop + 100) / 10;
        }
        $(isHtmlScrollable ? 'html' : 'body').stop().animate({
          scrollTop: positiontop
          },  speed);          
        }, 1000);
    }else{
      if(hrefval == "#") {
        positiontop = 0;
        speed = 900 + this$.offset().top /30;    
      }else{
        targetelement = $(hrefval);
        positiontop = targetelement.offset().top - 48;  //header の高さを調整
        //positiontop = targetelement.offset().top;
        speed = 900 + (positiontop + 100) / 10;
      }
      $(isHtmlScrollable ? 'html' : 'body').stop().animate({
        scrollTop: positiontop
      },  speed);      
    }
    $('.picture').each(function(index) {
       $(this).css({
         backgroundPosition: '0 0',
         height: '500px'         
         });  
      console.log(index + ': ' + $(this).css('background-position') + ' height: ' + $(this).height());    
    });       
        return false;
    });  
});

書き換えてみる

前述の例では「$('.picture').each(function(index)」の中に「window$.scroll(function() {」を記述していたが、反対に「window$.scroll(function() {」の中に「$('.picture').each(function(index)」を記述してみる。

どちらが効率的かは不明。この例の場合はそれほど違いは見られないが、場合によっては結果が異なる可能性もある。

サンプル 10

以下は変更部分のみ


var scroll_flag = true;
window$.scroll(function() {
var window_scrollTop = $(this).scrollTop();
if(scroll_flag){
if(window_scrollTop > div3_ot - 50) {
navi_a$.removeClass('selected');
$(navi_a$.get(3)).addClass('selected');
}else if(window_scrollTop > div2_ot - 50){
navi_a$.removeClass('selected');
$(navi_a$.get(2)).addClass('selected');
}else if(window_scrollTop > div1_ot - 50){
navi_a$.removeClass('selected');
$(navi_a$.get(1)).addClass('selected');
}else{
navi_a$.removeClass('selected');
}
}
//ここから
$('.picture').each(function(index) {
var this$ = $(this);
var positions = this$.offset();
var parent$ = this$.closest('.pallarax_parent');
var parentPositions = parent$.offset();
var inner$ = this$.find('.inner');
if(scroll_flag){
var window_st = window$.scrollTop();
var window_height =window$.height();

if ((window_st + window_height) > positions.top && ((positions.top + this$.height()) > window_st)) {
var offsetY = (window_st - positions.top) -(window_st - positions.top)/ this$.data('speed');
var bg_positions = '0 ' + offsetY + 'px';
this$.css('backgroundPosition', bg_positions);
}

if(window_st > positions.top && window_st < positions.top + this$.height() -inner$.height()) { var window_w = $(window).width(); var wrapper_w = $('#wrapper').width(); var offset_left = (window_w - wrapper_w) / 2; if(offset_left < 0) offset_left = 0; inner$.css({ position: 'fixed', top : '30px', left: offset_left + 30 + 'px' }); }else{ inner$.css({ position: 'absolute', top : '30px', left: '30px' }); } } }); }); [/code]