[Gaia] HTML5 Window Shade

- - posted in b2g, css3, firefoxos, gaia, html5, javascript | Comments

Window Shade

我手上的Samsung Galaxy S3的作業系統是Android 4.0.4。

當我收到facebook/google+/…等程式通知的時候,最上面那條bar會出現對應的圖示。

這時候可以透過將bar「滑下來」的動作拉出一個UI,裡面會有

  1. 快速設定
  2. 通知項目詳細列表

這個UX,Google把它稱為Window shade

神奇的地方在於當你在拉下或拉上面板的同時, 快速設定與通知項目是會先「掉下來」到底端, 到了項目的頂端出現之後才會黏在頂部。 (相信我你沒有特別注意這件事情,而且用久了會覺得理所當然)

GAIA也有notification panel

Firefox OS的UI – GAIA裡面也有定義功能列以及通知面板這件事, 這個功能正在開發中。

有一天我們收到某個contributor提交的pull request:

Gaia pull request#1898 節錄重要的修改如下:

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
38
39
40
41
42
43
44
45
46
  onTouchMove: function ut_onTouchMove(touch) {
    var screenHeight = this.overlay.getBoundingClientRect().height,
        gripBarHeight = this.gripBar.getBoundingClientRect().height,
        dy = -(this.startY - touch.pageY),
        newHeight;
    if (this.shown)
      dy += screenHeight;
    dy = Math.min(screenHeight, dy);

    if (dy > gripBarHeight) {
      var quickSettingsHeight = this.quickSettings.getBoundingClientRect().height;

      if (dy < quickSettingsHeight + gripBarHeight) {
        newHeight = screenHeight - quickSettingsHeight - gripBarHeight;
      } else {
        newHeight = screenHeight - dy;
      }
      this.quickSettings.style.MozTransition = '';
      this.quickSettings.style.MozTransform = 'translateY(' + newHeight + 'px)';
    }

    var style = this.overlay.style;
    style.MozTransition = '';
    style.MozTransform = 'translateY(' + dy + 'px)';
  },
  //....skiped...
  show: function ut_show(dy) {
    var alreadyShown = this.shown,
        trayStyle = this.overlay.style,
        quickSettingsStyle = this.quickSettings.style;

    trayStyle.MozTransition = '-moz-transform 0.2s linear';
    trayStyle.MozTransform = 'translateY(100%)';

    quickSettingsStyle.MozTransition = '-moz-transform 0.2s linear';
    quickSettingsStyle.MozTransform = 'translateY(0px)';

    this.shown = true;
    this.screen.classList.add('utility-tray');

    if (!alreadyShown) {
      var evt = document.createEvent('CustomEvent');
      evt.initCustomEvent('utilitytrayshow', true, true, null);
      window.dispatchEvent(evt);
    }
  }

原作者提到他做這件事是要讓quick-setting更快的出現。 這段code也已經被merge了。

不過就在昨天同事發現了一個關於功能列的issue

於是我開始看發生了什麼事。然後上面那兩個function耗費了我一個下午….XD

@colinfrei其實做了一件非常有趣但是很難從code裡面看出端倪的事情。 甚至也很難用語言來描述,不過我想試著說明發生了什麼事,怎麼做到的。

簡單的說,他利用兩個「不同」區塊的CSS3的Transition「同步化」來實現剛剛所提的Window Shade的行為。

CSS: Transition

參閱MDN: transition

CSS: Transform

參閱MDN: transform

真相其實是:

  1. utility-tray是一個絕對定位的div,平常位置定在螢幕上方-moz-calc(100% – UTILITY_TRAY_HEIGHT)
  2. 當你開始Touch的時候,根據你的移動距離計算translateY,使它產生位移

    你可以把-moz-transform: translateY(px)當成是top: px在CSS3的新招。 它提供了更快速的rendering — 當你想利用-moz-transition: -moz-transform來做位移動畫的時候。 這件事情可以另外寫一篇文章來說(挖洞的意味)。

  3. 當你放開拉bar的時候而且如果已經超過「要讓通知面板掉下來的合法距離」:

    • 設定一個MozTransitionutility-tray-overlay
    • 再設定一個MozTransitionquick-setting

4.讓utility-tray整個以某個定速掉下來到螢幕底端 + 讓quick-setting以同樣的定速往上移動到utility-tray的開端

= 於是就造成了quick-setting似乎黏在通知列下方不動但整個面板是往下掉的假象!

the story continues

不過可惜的是似乎沒有考慮到當通知面板要滑上去的時候,

「有物件的部份也應該留在頂端直到被拉bar撞到一起彈上去」這件事

於是我也仿造這個作法另外送了一個pull request

GAIA pull request#2181

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
  case 'transitionend':
    if (!this.shown) {
      this.screen.classList.remove('utility-tray');

      var overlayStyle = this.overlay;
      var firstShownStyle = this.firstShowStyle;
      firstShownStyle.MozTransition = '';

      if (this.phase2hide) {
        firstShownStyle.MozTransition = '-moz-transform 0.2s linear';
        firstShownStyle.MozTransform =
          'translateY(' + this.firstShownPosition + 'px)';
        overlayStyle.MozTransition = '-moz-transform 0.2s linear';
        overlayStyle.MozTransform = 'translateY(0)';

        // Check the transition event is triggered at firstShown.
        // If so, turn off the flag which represent for
        // 'The overlay has already reached the bottom of quick-setting'
        if (evt.target == this.firstShown)
          this.phase2hide = false;
      } else {
        // Reset position of this.firstShown
        this.firstShown.style.MozTransition = '';
        this.firstShown.style.MozTransform = 'translateY(0)';
      }
    }
    break;

利用transitionend event callback,透過連續兩次的Transition來:

  1. utility-tray先定速往上移動/讓quick-setting定速往下移動到底端。
  2. 等到utility-tray的transition結束時,會收到transitionend的事件。 這時候再讓utility-tray進行第二次transition…而目的地就是螢幕頂端。 同時quick-setting就不再動作了,讓它跟著utility-tray一起被往上卷走。

transitionend

參閱MDN: transitionend

the end?

其實UX還沒有定義這個行為,所以pull request本身沒有被接受。 不過…I did learn something from these codes:)

Comments