以前、iPhone5sのプロダクトページがリリースした頃に
『簡単にパララックス実装可能な軽量スクリプト「skrollr」&コンテンツごとにページスクロールするjQueryプラグイン「fullPage.js」「One Page Scroll」』と題して
1ページをフルスクリーン表示にして、画面全体をスクロールさせることで
1ページ分(1コンテンツ分)を画面遷移させるUIを実装できるjQueryプラグインを紹介しましたが
自分なりにもっと使いやすい形で実現できないかやってみた実験をご紹介してみます。
jQueryで1ページごとにスクロールして画面遷移させるUIを実装する方法
まずは動作サンプルから。
下記のサンプル画面でマウスホイールスクロールもしくは
画面右側のナビゲーションボタン等を使って
画面(ページ)を切り替えてみてください。
「jQueryで1ページごとにスクロールして画面遷移させるUIを実装する方法」サンプルを別枠で表示
以前話題になっていたiPhone5sのプロダクトページのように
1つのコンテンツを1画面内でフルスクリーン表示として
スクロールやボタンクリックで画面全体をスクロールさせて
次の画面を表示(遷移)する構成になっています。
ブラウザのウィンドウサイズをリサイズしても
1コンテンツは1画面内で展開する様になっています。
このサンプルファイルの全体構成について
まずはHTMLから。
◆HTML <div id="container"> <section id="stage1" class="stageBase"> <div class="fieldWrap"> <h1>jQuery OnePage Scroll</h1> <p>ここは1ページ目</p> </div><!--/.fieldWrap --> </section> <section id="stage2" class="stageBase"> <div class="fieldWrap"> <p>ここは2ページ目</p> <p><a href="http://black-flag.net" target="_blank">http://black-flag.net</a></p> </div><!--/.fieldWrap --> </section> <section id="stage3" class="stageBase"> <div class="fieldWrap"> <p>ここは3ページ目</p> </div><!--/.fieldWrap --> </section> <section id="stage4" class="stageBase"> <div class="fieldWrap"> <p>ここは4ページ目</p> </div><!--/.fieldWrap --> </section> <section id="stage5" class="stageBase"> <div class="fieldWrap"> <p>ここは5ページ目</p> <h2>COPYRIGHT © <a href="http://black-flag.net/">BLACKFLAG.NET</a> ALL RIGHTS RESERVED.</h2> </div><!--/.fieldWrap --> </section> </div><!--/#container-->
まず、すべてを囲う大枠ブロック要素を用意し(サンプルでは「#container」)
その中に1画面分のベースとなる任意のブロック要素(サンプルでは「.stageBase」)を
作成する画面数分、設置します。
※サンプルでは5画面設定にしてあるのでブロック要素「.stageBase」を5つ設置
◆HTML(枠組) <div id="container"> <section class="stageBase"></section> <section class="stageBase"></section> <section class="stageBase"></section> <section class="stageBase"></section> <section class="stageBase"></section> </div><!--/#container-->
サンプルでは各画面を囲うタグを<section>にしていますが
ここは<div>など別のタグで構成することも可能です。
これに対してCSSは以下の様になっています。
◆CSS
body {
position: relative;
overflow: hidden;
visibility: hidden;
}
/* #container
--------------------------- */
#container {
top: 0;
left: 0;
width: 100%;
position: absolute;
z-index: 1;
}
/* .stageBase
--------------------------- */
.stageBase {
width: 100%;
position: relative;
overflow: hidden;
}
.stageBase .fieldWrap {
padding: 100px 0 0 0;
text-align: center;
}
#stage1 {background:#fff;}
#stage2 {background:#eee;}
#stage3 {background:#ddd;}
#stage4 {background:#ccc;}
#stage5 {background:#bbb;}
/* #pageNav
--------------------------- */
#pageNav {
top: 0;
right: 25px;
width: 15px;
text-align: center;
position: fixed;
z-index: 2;
}
#pageNav ul {
width: 15px;
display: block;
}
#pageNav ul li {
padding-bottom: 5px;
width: 15px;
height: 15px;
display: block;
overflow: hidden;
}
#pageNav ul li a {
width: 15px;
height: 15px;
background: transparent url(../img/nav.png) no-repeat center center;
display: block;
}
#pageNav ul li.activeStage a {
background: transparent url(../img/nav_acv.png) no-repeat center center;
}
/* #pageDown
--------------------------- */
#pageDown {
bottom: 0;
left: 0;
width: 100%;
height: 40px;
text-align: center;
position: fixed;
overflow: hidden;
z-index: 3;
}
#pageDown a {
margin: 0 auto;
width: 30px;
height: 30px;
background: transparent url(../img/next_arw.png) no-repeat center center;
display: block;
}
bodyに対して「position: relative;」「overflow: hidden;」「visibility: hidden;」は
必須となっております。
全体を囲うブロック要素「#container」を「position: absolute;」にして
この要素を上下移動させて画面遷移をしている構成になります。
そして実際の動作スクリプトは以下の様になります。
◆SCRIPT
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
$(function(){
var setWrap = $('#container'),
setBase = $('.stageBase'),
scrollSpeed = 1000,
scrollEasing = 'swing',
downBtn = 'show', // 'show' or 'hide'
urlHash = 'on', // 'on' or 'off'
setHash = '!page';
var url = document.URL;
setWrap.append('<nav id="pageNav"><ul></ul></nav>');
setBase.each(function(){
$('#pageNav ul').append('<li><a href="javascript:void(0);"></a></li>');
});
if(downBtn == 'show'){
setWrap.append('<div id="pageDown"><a href="javascript:void(0);"></a></div>');
}
var coreNav = $('#pageNav'),
setNav = coreNav.find('ul'),
navList = setNav.find('li'),
navLength = navList.length;
setNav.find('li:first').addClass('activeStage');
$('body').attr('data-page','1');
$(window).load(function(){
// StageHeight
$(window).resize(function(){
var wdHeight = $(window).height();
setBase.css({height:wdHeight});
var resizeContTop = parseInt(setWrap.css('top'));
if(resizeContTop === 0){
setWrap.css({top:'0'});
} else {
var activeStagePos = setNav.find('li.activeStage');
activeStagePos.each(function(){
var posIndex = navList.index(this);
setWrap.css({top:-(wdHeight*posIndex)});
});
}
coreNav.each(function(){
var navHeight = $(this).height();
$(this).css({top:((wdHeight)-(navHeight))/2});
});
}).resize();
// MouseWheelEvent
var mousewheelevent = 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll';
$(document).on(mousewheelevent,function(e){
if(!(setWrap.is(':animated'))){
e.preventDefault();
var delta = e.originalEvent.deltaY ? -(e.originalEvent.deltaY) : e.originalEvent.wheelDelta ? e.originalEvent.wheelDelta : -(e.originalEvent.detail);
if (delta < 0){
motionDown();
} else {
motionUp();
}
}
});
// FlickEvent
var isTouch = ('ontouchstart' in window);
setWrap.on(
{'touchstart': function(e){
if(setWrap.is(':animated')){
e.preventDefault();
} else {
this.pageY = (isTouch ? event.changedTouches[0].pageY : e.pageY);
this.topBegin = parseInt($(this).css('top'));
this.top = parseInt($(this).css('top'));
this.touched = true;
}
},'touchmove': function(e){
if(!this.touched){return;}
e.preventDefault();
this.top = this.top - (this.pageY - (isTouch ? event.changedTouches[0].pageY : e.pageY));
this.pageY = (isTouch ? event.changedTouches[0].pageY : e.pageY);
},'touchend': function(e){
if (!this.touched) {return;}
this.touched = false;
if(((this.topBegin)-30) > this.top){
motionDown();
} else if(((this.topBegin)+30) < this.top){
motionUp();
}
}
});
// ScrollUpEvent
function motionUp(){
var stageHeightU = setBase.height(),
contTopUp = parseInt(setWrap.css('top')),
moveTopUp = contTopUp + stageHeightU;
if(!(contTopUp === 0)){
setWrap.stop().animate({top:moveTopUp},scrollSpeed,scrollEasing);
setNav.find('li.activeStage').removeClass('activeStage').prev().addClass('activeStage');
var acvStageP = parseInt($('body').attr('data-page')),
setPrev = acvStageP-1;
$('body').attr('data-page',setPrev);
if(downBtn == 'show'){
pagePos();
}
}
if(urlHash == 'on'){
replaceHash();
}
}
// ScrollDownEvent
function motionDown(){
var stageHeightD = setBase.height(),
contTopDown = parseInt(setWrap.css('top')),
moveTopDown = contTopDown - stageHeightD;
var contHeight = setWrap.height(),
maxHeightAdj = -(contHeight - stageHeightD);
if(!(contTopDown == maxHeightAdj)){
setWrap.stop().animate({top:moveTopDown},scrollSpeed,scrollEasing);
setNav.find('li.activeStage').removeClass('activeStage').next().addClass('activeStage');
var acvStageN = parseInt($('body').attr('data-page')),
setNext = acvStageN+1;
$('body').attr('data-page',setNext);
if(downBtn == 'show'){
pagePos();
}
}
if(urlHash == 'on'){
replaceHash();
}
}
// SideNaviClick
navList.click(function(){
var crtIndex = navList.index(this),
crtHeight = $(window).height();
setWrap.stop().animate({top:-(crtHeight*crtIndex)},scrollSpeed,scrollEasing);
setNav.find('li.activeStage').removeClass('activeStage');
$(this).addClass('activeStage');
$('body').attr('data-page',crtIndex+1);
if(downBtn == 'show'){
pagePos();
}
if(urlHash == 'on'){
replaceHash();
}
});
// PageDownBtnClick
$('#pageDown a').click(function(){
if(!(setWrap.is(':animated'))){
var navActive = setNav.find('li.activeStage');
navActive.each(function(){
var navIndex = navList.index(this),
setNav = navIndex+1;
if(!(setNav == navLength)){
$(this).next().click();
}
});
if(urlHash == 'on'){
replaceHash();
}
}
});
function pagePos(){
var pnAcv = coreNav.find('li.activeStage');
pnAcv.each(function(){
var pnIndexN = navList.index(this),
pnCountN = pnIndexN+1;
if(pnCountN == navLength){
$('#pageDown').css({display:'none'});
} else {
$('#pageDown').css({display:'block'});
}
});
}
// HashReplace
function replaceHash(){
var pnAcv = coreNav.find('li.activeStage');
pnAcv.each(function(){
var pnIndexN = navList.index(this),
pnCountN = pnIndexN+1;
location.hash = setHash + pnCountN;
});
}
if(urlHash == 'on'){
replaceHash();
}
// OpeningFade
$('body').css({visibility:'visible',opacity:'0'}).animate({opacity:'1'},1000);
// LoadPageMove
if(url.indexOf(setHash) !== -1){
var numSplit = ((url.split(setHash)[1])-1);
navList.eq(numSplit).click();
}
});
// HashChangeEvent
if(urlHash == 'on'){
$(window).on('hashchange',function(){
var stateUrl = document.URL,
hashSplit = ((stateUrl.split(setHash)[1])-1);
navList.eq(hashSplit).click();
});
}
});
</script>
スクリプト開始部分にある設定値の内容は以下の様になっています。
| var setWrap = $(‘#container’) | すべてを囲うブロック要素(クラスでも可) |
|---|---|
| setBase = $(‘.stageBase’) | 1画面を囲うブロック要素 |
| scrollSpeed = 1000 | スクロール移動スピード |
| scrollEasing = ‘swing’ | スクロール移動する際のイージング |
| downBtn = ‘show’ | 画面下にNEXTボタンの設置有無(表示 = show, 非表示 = hide) |
| urlHash = ‘on’ | ハッシュをつけてのURL操作の有無(有 = on, 無 = off) |
| setHash = ‘!page’ | URLの後ろに付けるハッシュ用テキスト |
これらの設定値を変更することで微調整が可能になっています。
サンプル画面で表示している画面下の▼ボタンは
「downBtn」の指定を変えてもらうことで、表示/非表示を切り替えることができます。
※見た目の装飾についてはCSS側で「#pageDown、#pageDown a」を調整します。
画面右側のナビゲーションボタンについては
作成する画面数によって自動でリストを生成するようになっており、
見た目等を変える場合はCSS側で「#pageNav」の部分を調整します。
マウスホイールでのスクロールと別に
フリック(スワイプ?)動作での制御も入れてありますので
iPhone/iPadなどのスマホ・タブレットでも動作すると思います。
「urlHash = ‘on’」ここを“on”にすることで
URLの最後に各画面を個別に判別する為の「#(ハッシュ)」が付与されるようになります。
付与するテキストは「#」だけでなく識別名を入れられるように
「setHash = ‘!page’」の値で設定することが可能です。
※「!」は必須ではないので「#」だけにしたい時は「!」を付けずにテキストのみで記述してください。
ここで設定している「#(ハッシュ)」をhasheventを使うことで、ページ内で画面遷移をした後、
ブラウザでの「戻る」「進む」ボタンでも画面が行き来(スクロール移動)するようになっています。
このページ内でのブラウザの「戻る」「進む」ボタン動作が必要ない場合には
「urlHash」の指定を“off”にしてください。
固有のURLから特定画面(○番目の画面)を指定する場合は
このハッシュの後ろに表示する画面番号を付けたURLを使って指定することが可能です。
※画面表示後、スクロールアニメーションが実行されます。
※この動作に関しては「urlHash」の指定を“off”にしていても実行されます。
サンプルページの3ページ目を表示【http://black-flag.net/devel/jQueryOnePageScroll/#!page3】
スクリプト全体は画面スクロール遷移動作の枠組みのみとなっているので
それぞれ各画面内のレイアウト等については
CSSや別途JSなどのスクリプトを使って形成する形になります。
スクリプト自体はシンプルではないかもしれませんが
HTMLとCSS構成に関してはカスタマイズしやすい形にはなっているかな、、と思っております。
jQueryを使って1ページごとにスクロールして画面遷移させるUIを実装する際にぜひ。。。