Takahiro Octopress Blog

-1から始める情弱プログラミング

UIWebView内でheaderを固定して滑らかなスクロールを実現しよう!

header固定をしたスクロールは案外難しい!!

本日はUIWebViewを全面に貼り付けた上で、ヘッダーを固定して滑らかなスクロールを実現する方法について書きたいと思います。
もし、ヘッダーをネイティブで作成して、中身をWebで作成すれば簡単に実現できます。しかし、ネイティブとWebのハイブリットアプリを目指すとAndroidへの対応が手間となるため一長一短な面があります。今回はあえて全面UIWebViewに拘りたいと思います。

position: fixedだとうまくいかない!?

まず、Webでヘッダーを固定することを考えたときに真っ先に思い浮かぶものはposition: fixedを利用することだと思います。

サンプルHTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
  <head>
      <meta charset="UTF-8" content="text/css" http-equiv="Content-Style-Type" />
      <meta content="width=device-width, user-scalable=no,minimum-scale=0.5, maximum-scale=1.0" name="viewport" />
      <link href="./main.css" media="all" rel="stylesheet" type="text/css" />
      <title>サンプル</title>
  </head>
  <body>
      <div id="header">ヘッダー</div>
      <div id="mainContents">
          <div id="main">
              contents
              <br>
              contents
              <br>
              ...
              contents
              <br>
          </div>
      </div>
  </body>
</html>

サンプルCSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
html, body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
}

#header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 64px;
  z-index: 2;
}

#mainContents {
  position: relative;
  top: 64px;
  width: 100%;
  height: 100%;
  -webkit-overflow-scrolling: touch;
  z-index: 1;
}

確かにこの設定であればヘッダーを固定して中身を滑らかにスクロールすることが可能になります。そして、iPhoneのSafariからその画面を見るのであればさして違和感はないでしょう。しかし、これをUIWebView上で見ると画面のバウンドを止められないことに気がつきます。これは結構な違和感です。
このバウンドを以下のようにネイティブソース側から止めることを考えました。

ネイティブソース(UIWebViewの設定部分)

1
2
3
4
5
6
7
- (void)viewDidLoad {
  [super viewDidLoad];
  uiWebView.delegate = self;
  // バウンドしないように設定
  [[(UIScrollView *)[uiWebView subviews] objectAtIndex:0] setBounces:NO];
  ...
}

しかし、これだとCSSで設定した-webkit-overflow-scrolling: touchの効果がなくなってしまいます。なので別の方法を考えました。

position: absoluteを使う!!

position: fixed;は使いません。position: absolute;を使います。以下、踏まえて書き直します。

サンプルHTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
  <head>
      <meta charset="UTF-8" content="text/css" http-equiv="Content-Style-Type" />
      <meta content="width=device-width, user-scalable=no,minimum-scale=0.5, maximum-scale=1.0" name="viewport" />
      <link href="./main.css" media="all" rel="stylesheet" type="text/css" />
      <title>サンプル</title>
  </head>
  <body>
      <div id="wrapper">
          <div id="header">ヘッダー</div>
          <div id="mainContents">
              <div id="main">
                  contents
                  <br>
                  contents
                  <br>
                  ...
                  contents
                  <br>
              </div>
          </div>
      </div>
  </body>
</html>

サンプルCSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
html, body {
  padding: 0;
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

#wrapper {
  position: relative;
  width: 100%;
  height: 100%;
}

#header {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 64px;
  z-index: 2;
}

#mainContents {
  position: relative;
  top: 64px;
  width: 100%;
  height: 100%;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  z-index: 1;
}

#main {
  position: relative;
  padding: 64px 0;
}

これでほとんどの場合、バウンドしなくなります。しかも、滑らかなスクロールを保つこともできます。
ただし、ヘッダーをタッチムーブすると…バウンドしてしまいます!!

う〜ん。これは果たして無理なのか!?と思ったところで思いつきました。touchイベントを無効化しましょう!!
jQueryを使っている前提で下記を紹介します。

サンプルJS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

jQuery( function($) {
  
  $("#header").bind("touchstart", function() {
      event.preventDefault();
  });
  
  $("#header").bind("touchmove", function() {
      event.preventDefault();
  });

  $("#header").bind("touchend", function() {
      event.preventDefault();
  });

});

これでバウンドせずに滑らかなスクロールを保つことができました。

と思ったのですが、画面の両端を触っているとバウンドが発生してしまいます…。要調査ですね…。

Comments