モバイル デバイスで料理中に、レシピの手順の途中で画面がオフになるほど困ることはありません。料理サイト BettyCrocker.com が Wake Lock API を使用して、このような事態を防いだ方法をご覧ください。
Betty Crocker は、100 年近くにわたり、アメリカで最新の調理手順と信頼できるレシピ開発のソースとして使用されてきました。1997 年にリリースされたサイト BettyCrocker.com は、現在、毎月 1, 200 万人以上のユーザーが訪れています。Wake Lock API を実装した後、ウェイクロック ユーザーの購入意向の指標は、すべてのユーザーと比較して約 300% 高くなりました。
サポートが終了した iOS アプリと Android アプリ
2014 年に大きな注目を集めてリリースされた Betty Crocker は、優先度が下がった後、最近 Apple App Store と Google Play ストアからアプリを削除しました。長い間、Betty Crocker チームは、iOS/Android アプリではなくモバイルサイトに新機能を追加することを優先してきました。iOS アプリと Android アプリが作成された技術プラットフォームが古く、アプリの今後の更新とメンテナンスに対応できるリソースが企業にありませんでした。また、ウェブアプリはトラフィック面で客観的に大幅に大きく、よりモダンで、拡張も容易でした。
ただし、iOS アプリと Android アプリには、ユーザーに好評だったキラー機能が 1 つありました。
ミレニアル世代向けの料理のヒント: @BettyCrocker モバイルアプリでは、レシピを参考にしているときに画面が暗くなったりロックされたりすることはありません。-@AvaBeilke
80% の人がキッチンでデバイスを使って料理をしているが、画面の明るさの調節とロックが問題になっている。 @BettyCrocker はどのような対応をしましたか? ユーザーがレシピ内にいるときに画面が暗くならないようにアプリを更新しました。 -@KatieTweedy
Wake Lock API でウェブにキラー機能を導入する
デバイスで料理をするときに、画面がオフになっているときに汚れた手や鼻で画面に触れる必要以上にイライラすることはありません。Betty Crocker は、iOS/Android アプリの優れた機能をウェブアプリに移植する方法を模索していました。そこで、Project Fugu と Wake Lock API について学びました。

Wake Lock API を使用すると、デバイスの画面が暗くなったり、ロックされたりすることを防ぐことができます。この機能により、これまで iOS/Android アプリが必要だった新しいエクスペリエンスを実現できます。Wake Lock API を使用すると、ハッキングや電力消費の多い回避策の必要性が軽減されます。
wake lock のリクエスト
ウェイクロックをリクエストするには、WakeLockSentinel オブジェクトを返す navigator.wakeLock.request() メソッドを呼び出す必要があります。このオブジェクトは、センチネル値として使用します。ブラウザはさまざまな理由(バッテリー残量が少ないなど)でリクエストを拒否する可能性があるため、呼び出しを try…catch ステートメントでラップすることをおすすめします。
wake lock の解除
また、ウェイクロックを解除する方法も必要です。これは、WakeLockSentinel オブジェクトの release() メソッドを呼び出すことで行います。一定の時間が経過した後にウェイクロックを自動的に解除するには、次の例に示すように、window.setTimeout() を使用して release() を呼び出します。
// The wake lock sentinel.
let wakeLock = null;
// Function that attempts to request a wake lock.
const requestWakeLock = async () => {
  try {
    wakeLock = await navigator.wakeLock.request('screen');
    wakeLock.addEventListener('release', () => {
      console.log('Wake Lock was released');
    });
    console.log('Wake Lock is active');
  } catch (err) {
    console.error(`${err.name}, ${err.message}`);
  }
};
// Request a wake lock…
await requestWakeLock();
// …and release it again after 5s.
window.setTimeout(() => {
  wakeLock.release();
  wakeLock = null;
}, 5000);
実装
新しいウェブアプリでは、ユーザーはレシピを簡単に移動したり、手順を完了したり、画面をロックせずに離れたりできるようになります。この目標を達成するために、チームはまず、概念実証として、また UX のフィードバックを得るために、簡単なフロントエンド プロトタイプを作成しました。
プロトタイプが有用であることが判明した後、すべてのブランド(BettyCrocker、Pillsbury、Tablespoon)で共有できる Vue.js コンポーネントを設計しました。Betty Crocker のみが iOS アプリと Android アプリを持っていましたが、3 つのサイトはコードベースを共有しているため、コンポーネントを 1 回実装して、すべてのサイトにデプロイできました(以下のスクリーンショットをご覧ください)。
 
   
   
  新しいサイトの最新のフレームワークに基づいてコンポーネントを開発する際は、MVVM パターンの ViewModel レイヤに重点を置きました。また、サイトの従来と新しいフレームワークで機能を有効にするため、相互運用性を念頭に置いてプログラミングを行いました。
視認性とユーザビリティを把握するため、Betty Crocker は、ウェイクロックのライフサイクルのコアイベントのアナリティクス トラッキングを統合しました。チームは、機能管理を使用して、最初の本番環境ロールアウトのためにウェイクロック コンポーネントを 1 つのサイトにデプロイし、使用状況とページの健全性をモニタリングした後、残りのサイトに機能をデプロイしました。Google は、このコンポーネントの使用状況に基づいてアナリティクス データを引き続きモニタリングします。
ユーザーのフェイルセーフとして、チームは、1 時間操作がないとウェイクロックを無効にする強制タイムアウトを作成しました。最終的に決定した実装は、サイト内のすべてのレシピ ページに切り替えスイッチを設置するという短期的なものです。長期的には、レシピ ページの表示を刷新する予定です。
wake lock コンテナ
var wakeLockControl = () => {
  return import(/* webpackChunkName: 'wakeLock' */ './wakeLock');
};
export default {
  components: {
    wakeLockControl: wakeLockControl,
  },
  data() {
    return {
      config: {},
      wakeLockComponent: '',
    };
  },
  methods: {
    init: function(config) {
      this.config = config || {};
      if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
        this.wakeLockComponent = 'wakeLockControl';
      } else {
        console.log('Browser not supported');
      }
    },
  },
};
ウェイクロック コンポーネント
<template>
  <div class="wakeLock">
    <div class="textAbove"></div>
    <label class="switch" :aria-label="settingsInternal.textAbove">
      <input type="checkbox" @change="onChange()" v-model="isChecked">
      <span class="slider round"></span>
    </label>
  </div>
</template>
<script type="text/javascript">
  import debounce from 'lodash.debounce';
  const scrollDebounceMs = 1000;
  export default {
    props: {
      settings: { type: Object },
    },
    data() {
      return {
        settingsInternal: this.settings || {},
        isChecked: false,
        wakeLock: null,
        timerId: 0,
      };
    },
    created() {
      this.$_raiseAnalyticsEvent('Wake Lock Toggle Available');
    },
    methods: {
      onChange: function() {
        if (this.isChecked) {
          this.$_requestWakeLock();
        } else {
          this.$_releaseWakeLock();
        }
      },
      $_requestWakeLock: async function() {
        try {
          this.wakeLock = await navigator.wakeLock.request('screen');
          //Start new timer
          this.$_handleAbortTimer();
          //Only add event listeners after wake lock is successfully enabled
          document.addEventListener(
            'visibilitychange',
            this.$_handleVisibilityChange,
          );
          window.addEventListener(
            'scroll',
            debounce(this.$_handleAbortTimer, scrollDebounceMs),
          );
          this.$_raiseAnalyticsEvent('Wake Lock Toggle Enabled');
        } catch (e) {
          this.isChecked = false;
        }
      },
      $_releaseWakeLock: function() {
        try {
          this.wakeLock.release();
          this.wakeLock = null;
          //Clear timer
          this.$_handleAbortTimer();
          //Clean up event listeners
          document.removeEventListener(
            'visibilitychange',
            this.$_handleVisibilityChange,
          );
          window.removeEventListener(
            'scroll',
            debounce(this.$_handleAbortTimer, scrollDebounceMs),
          );
        } catch (e) {
          console.log(`Wake Lock Release Error: ${e.name}, ${e.message}`);
        }
      },
      $_handleAbortTimer: function() {
        //If there is an existing timer then clear it and set to zero
        if (this.timerId !== 0) {
          clearTimeout(this.timerId);
          this.timerId = 0;
        }
        //Start new timer; Will be triggered from toggle enabled or scroll event
        if (this.isChecked) {
          this.timerId = setTimeout(
            this.$_releaseWakeLock,
            this.settingsInternal.timeoutDurationMs,
          );
        }
      },
      $_handleVisibilityChange: function() {
        //Handle navigating away from page/tab
        if (this.isChecked) {
          this.$_releaseWakeLock();
          this.isChecked = false;
        }
      },
      $_raiseAnalyticsEvent: function(eventType) {
        let eventParams = {
          EventType: eventType,
          Position: window.location.pathname || '',
        };
        Analytics.raiseEvent(eventParams);
      },
    },
  };
</script>
結果
Vue.js コンポーネントは 3 つのサイトすべてにデプロイされ、大きな成果を上げています。2019 年 12 月 10 日から 2020 年 1 月 10 日までの期間、BettyCrocker.com では次の指標が報告されました。
- Wake Lock API に対応したブラウザを使用している Betty Crocker の全ユーザーのうち、3.5% がこの機能をすぐに有効にしました。これは、上位 5 個のアクションの 1 つです。
- ウェイクロックを有効にしたユーザーのセッション継続時間は、有効にしなかったユーザーの 3.1 倍でした。
- ウェイクロックを有効にしたユーザーの離脱率は、ウェイクロック機能を使用しないユーザーの離脱率よりも 50% 低くなっています。
- 購入意向の指標は、ウェイクロックを使用しているユーザーで、全ユーザーと比較して約 300% 高くなっています。
3.1×
セッションの継続時間の長さ
50%
直帰率が低下
300%
購入意向が高いことを示す指標
まとめ
Betty Crocker は、Wake Lock API を使用して素晴らしい結果を得ています。この機能をご自身でテストするには、任意のサイト(BettyCrocker、Pillsbury、Tablespoon)でお気に入りのレシピを検索し、[料理中に画面が暗くならないようにする] をオンにします。
ウェイクロックのユースケースは、レシピ サイトだけではありません。その他の例としては、バーコードがスキャンされるまで画面をオンにしておくことが必要な搭乗券やチケットのアプリ、画面を常にオンにしておくことが必要なキオスク スタイルのアプリ、プレゼンテーション中に画面がスリープしないようにするウェブベースのプレゼンテーション アプリなどがあります。
Wake Lock API について知っておくべきことは、このサイトの包括的な記事にまとめられています。ぜひお役立てください。
謝辞
生地をこねる人の写真は、Unsplash の Julian Hochgesang 提供。
 
 
        
        