Wake Lock API の事例紹介: BettyCrocker.com での購入意向インジケーターが 300% 増加

モバイル デバイスで調理するとき、レシピのステップの途中で画面がオフになることほど最悪なものはありません。料理サイトの BettyCrocker.com が、Wake Lock API を使用してこのような事態を回避した方法をご覧ください。

約 1 世紀にわたり、Betty Crocker は現代の料理指導と信頼性の高いレシピ開発においてアメリカの情報源であり続けています。1997 年に開設された同社のサイト BettyCrocker.com は現在、月間 1, 200 万人を超える訪問者に利用されています。Wake Lock API の実装後、すべてのユーザーと比較して wake lock ユーザーの購入意向の指標が約 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 FuguWake Lock API について学びました。

小麦粉をまぶしたキッチン テーブルで生地をこねている人

Wake Lock API は、デバイスの画面が暗くなったりロックされたりしないようにする手段を提供します。この機能により、これまで iOS/Android アプリが必要だった新たなエクスペリエンスが可能になります。Wake Lock API を使用すると、ハックや電力を大量に消費する可能性のある回避策の必要性を減らすことができます。

wake lock のリクエスト

wake lock をリクエストするには、WakeLockSentinel オブジェクトを返す navigator.wakeLock.request() メソッドを呼び出す必要があります。このオブジェクトは Sentinel 値として使用します。ブラウザはさまざまな理由(たとえば、バッテリー残量が少ないなど)でリクエストを拒否する可能性があるため、呼び出しを try…catch ステートメントでラップすることをおすすめします。

wake lock の解除

また、ウェイクロックを解除する方法も必要です。ウェイクロックを解除するには、WakeLockSentinel オブジェクトの release() メソッドを呼び出します。一定の時間が経過した後に wake lock を自動的に解除する場合は、次の例に示すように、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 入力を収集するために、簡単なフロントエンド プロトタイプを構築しました。

プロトタイプが役に立った後、すべてのブランド(BettyCrockerPillsburyTablespoon)で共有できる Vue.js コンポーネントを設計しました。iOS アプリと Android アプリを提供しているのは Betty Crocker だけですが、3 つのサイトはコードベースを共有しているため、以下のスクリーンショットに示すように、コンポーネントを一度実装すれば、どこにでもデプロイできます。

BettyCrocker.com の wake lock の切り替え
BettyCrocker.com の wake lock の切り替え。
Pillsbury.com の wake lock の切り替え
Pillsbury.com の wake lock の切り替え。
Tablespoon.com の wake lock の切り替え
Tablespoon.com の wake lock の切り替え。

新しいサイトのモダナイズされたフレームワークに基づいてコンポーネントを開発する場合、MVVM パターンの ViewModel レイヤに重点を置きました。また、チームは相互運用性を念頭に置いて、サイトの新旧のフレームワークで機能を有効にするためのプログラムも作成しました。

視認性とユーザビリティを追跡するために、Betty Crocker は wake lock ライフサイクルのコアイベントの分析トラッキングを統合しました。チームは機能管理を利用して、最初の本番環境ロールアウトで wake lock コンポーネントを 1 つのサイトにデプロイしました。その後、使用状況とページの状態をモニタリングした後、機能を残りのサイトにデプロイしました。チームは、このコンポーネントの使用状況に基づいて分析データのモニタリングを継続します。

チームはユーザー向けのフェイルセーフとして、1 時間操作がなかった場合に wake lock を無効にする強制タイムアウトを作成しました。最終的には、サイトのすべてのレシピページの切り替えスイッチを短期的に実装することにしました。長期的には、レシピページ ビューの改善を視野に入れています。

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');
      }
    },
  },
};

wake lock コンポーネント

<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 位のアクションとなっています。
  • wake lock を有効にしたユーザーのセッション継続時間は、有効にしなかったユーザーと比較して 3.1 倍長くなりました。
  • wake lock を有効にしたユーザーの直帰率は、wake lock 機能を使用していないユーザーと比較して 50% 低くなりました。
  • ウェイクロック ユーザーの購入意向インジケーターは、すべてのユーザーと比較して約 300% 高くなりました。

3.1×

セッション継続時間が長くなる

50%

直帰率の低下

300%

購入意向インジケーターが高い

まとめ

Betty Crocker は、Wake Lock API を使用して素晴らしい成果を上げました。 この機能を試すには、サイト(BettyCrockerPillsburyTablespoon)でお気に入りのレシピを検索し、[料理中に画面が暗くならないようにする] をオンにします。

wake lock のユースケースはレシピサイトに限らず、他にも、バーコードがスキャンされるまで画面を表示し続ける必要がある搭乗券やチケットのアプリ、画面を常にオンにするキオスクスタイルのアプリ、プレゼンテーション中に画面がスリープしないようにするウェブベースのプレゼンテーション アプリなどがあります。

Wake Lock API について知っておくべきことは、このサイトにある総合的な記事にまとめました。読書をお楽しみください。

謝辞

生地をこねる人(写真提供: Julian HochgesangUnsplash