サーバータイムを使ったミリ秒対応のカウントダウン
ティザーサイト等で良く使われる「オープンまであと何日」みたいなカウントダウンを作ってみる。Flashだけで作るとローカルタイムに依存するので、ユーザーに時計を進められるとネタばれすることも。たまに見かけるけど。上のデモはアクセスした日から常に1ヶ月先をカウントダウンします。残りゼロになっても、何も起きませんよ。
サーバーから時間を取得した後にFlash側で経過時間を足して、目的の時間から差分を取ることでどのPCから見てもカウントダウンの時刻を同期することができる。
カウンタークラスにしてみた。ドキュメントクラスでは下記のように使います。
package
{
import flash.display.Sprite;
import flash.text.TextField;
import info.five.net.Counter;
import info.five.events.CountEvent;
/**
* ...
* @author 5ive
*/
public class Main extends Sprite
{
public function Main():void
{
var d:Date = new Date();
var ctr:Counter = new Counter(d.fullYear, d.month + 2, d.date);
ctr.getServerTime("servertime.php");
ctr.addEventListener(Counter.CHANGE, onChange);
ctr.addEventListener(Counter.FINISH, onFinish);
}
private function onFinish(e:CountEvent):void
{
trace("finish!");
}
private function onChange(e:CountEvent):void
{
countText.text = e.day + "d " + e.hour + "h " + e.minute + "m " + e.second + "s " + e.milisecond + "ms";
}
}
}
new Counter()の引数でカウントさせたい未来の日付と時間を渡す。getServerTime()メソッドでPHPへのパスを渡す。後はCHANGEとFINISHイベントをリスナーに登録して時間が流れるのを待つだけ。
Counter.as
package info.five.net
{
import flash.display.Sprite;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLVariables;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.EventDispatcher;
import flash.utils.getTimer;
import info.five.events.CountEvent;
/**
* ...
* @author 5ive
*/
public class Counter extends EventDispatcher
{
public static const CHANGE:String = "change";
public static const FINISH:String = "finish";
private var _loader:URLLoader;
private var _serverTime:Number;
private var _swfTime:Number;
private var _futureDate:Date;
private var _sp:Sprite;
public function Counter(year:uint, month:uint, day:uint, hour:uint = 0, monuites:uint = 0, second:uint = 0, milisecond:uint = 0):void
{
_sp = new Sprite();
_futureDate = new Date(year, month - 1, day, hour, monuites, second, milisecond);
}
//------------------------------
// サーバーへの問い合わせ
//------------------------------
public function getServerTime(url:String):void
{
//-----[キャッシュ対策]
var date:Date = new Date();
url += "?t=" + date.getTime();
//-----[サーバーの時間取得]
_loader = new URLLoader();
_loader.addEventListener(Event.COMPLETE, onComplete);
_loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
_loader.load(new URLRequest(url));
}
//------------------------------
// IOErrorEvent
//------------------------------
private function onError(e:IOErrorEvent):void
{
trace("error=" + e.text);
}
//------------------------------
// サーバーの時間取得
//------------------------------
private function onComplete(e:Event):void
{
var urlVariables:URLVariables = new URLVariables(_loader.data);
_serverTime = Number(urlVariables.returnValue);
_swfTime = getTimer();
//-----[URLLoaderの削除]
_loader.removeEventListener(Event.COMPLETE, onComplete);
_loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
_loader.data = null;
_loader = null;
//-----[タイマーの開始]
if (_futureDate.getTime() < _serverTime)
{
trace("[error]:カウントする日付が過去を指定しています。");
}
else
{
if (_sp != null) _sp.addEventListener(Event.ENTER_FRAME, onTicks);
}
}
//------------------------------
// タイマーの更新
//------------------------------
private function onTicks(e:Event):void
{
var passage:Number = _serverTime + (getTimer() - _swfTime);
var diff:Number = _futureDate.getTime() - passage;
var day:* = Math.floor(diff / (24 * 60 * 60 * 1000));
var total:* = Math.floor(diff / (60 * 60 * 1000));
var hour:* = total - (day * 24);
var minute:* = Math.floor(diff / (60 * 1000)) - (total * 60);
var second:* = Math.ceil(diff / 1000) - ((minute * 60) + (total * 60 * 60)) - 1;
var milisecond:String = diff.toString().substr(diff.toString().length - 3, 3);
//-----[2桁で表示]
day = String(day + 100).substr(1, 2);
hour = String(hour + 100).substr(1, 2);
minute = String(minute + 100).substr(1, 2);
second = String(second + 100).substr(1, 2);
//-----[イベントクラスに値を格納]
var ce:CountEvent = new CountEvent(CountEvent.CHANGE);
if (diff > 100)
{
ce.day = day;
ce.hour = hour;
ce.minute = minute;
ce.second = second;
ce.milisecond = milisecond;
dispatchEvent(ce);
}
else
{
ce.day = "00";
ce.hour = "00";
ce.minute = "00";
ce.second = "00";
ce.milisecond = "000";
dispatchEvent(ce);
dispatchEvent(new CountEvent(CountEvent.FINISH));
kill();
}
}
//------------------------------
// データの破棄
//------------------------------
public function kill():void
{
_sp.removeEventListener(Event.ENTER_FRAME, onTicks);
_sp = null;
}
}
}
Counterクラスではサーバータイムを取得した瞬間にENTER_FRAMEを発動。そこからFlash内での経過ミリ秒を加算していき、目的の時間との差分を計算してCountEventクラスに各時間を格納させる。そのイベントをドキュメントクラスに返して時間を取得させる。
CountEvent.as
package info.five.events
{
import flash.events.Event;
public class CountEvent extends Event
{
public static const CHANGE:String = "change";
public static const FINISH:String = "finish";
public var day:String;
public var hour:String;
public var minute:String;
public var second:String;
public var milisecond:String;
//------------------------------
// コンストラクタ
//------------------------------
public function CountEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false):void
{
super(type, bubbles, cancelable);
}
//------------------------------
// クローン
//------------------------------
public override function clone():Event
{
return new CountEvent(type, bubbles, cancelable);
}
}
}
CountEventクラスには「day」、「hour」、「minute」、「second」、「milisecond」プロパティが用意されているので、ドキュメントクラス側で必要に応じて取得して表示させて下さい。
serverttime.php
<?php
$stamp = microtime();
$ary = split(" ", $stamp);
echo "returnValue=".(string)$ary[1].(string)(int)($ary[0] * 1000);
?>
最後にサーバーに置くPHP。Flash側のgetTimer()のミリ秒と、PHP側のmicrotime()のミリ秒で桁が違うので、桁合わせのためにちょっとだけごにょごにょしてます。microtime()でミリ秒を取得すると小数点付きで分割して計算されるので、一度文字列にしてFlash側に合わせるように変換かけてます。