2009.09.08
category
comments

サーバータイムを使ったミリ秒対応のカウントダウン

ティザーサイト等で良く使われる「オープンまであと何日」みたいなカウントダウンを作ってみる。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側に合わせるように変換かけてます。

2009.03.12
category
comments

マウス追従でスクロールするインターフェイス

よく見るインターフェイスで、マウスを動かすだけでスクロールする動きを実装してみた。すごくシンプルに考えてみるとモーション自体のスクリプトはこの3行でいける。

percent = scrollContainer.stage.mouseX / (scrollLength - 1);
pos = (scrollLength - (scrollContainer.width + _space) - _space) * percent;
scrollContainer.x = scrollContainer.x - ((scrollContainer.x - _space - pos) / _friction);

ステージの横幅に対してマウスのx座標が何%の位置にいるのかを割り出して計算させれば意外なほどあっさり実装できる。条件分岐もしなくていいし。クラスにしてみたけど、汎用的に使うにはもうちょいチューニングが必要かも。

ScrollOver.as

package
{
	import flash.display.DisplayObjectContainer;
	import flash.events.Event;

	public class ScrollOver
	{
		private var scrollContainer:DisplayObjectContainer;
		private var scrollLength:uint;
		private var vertical:Boolean;
		private var _space:uint = 0;
		private var _friction:uint = 15;

		public function set space(value:uint):void { _space = value; }
		public function set friction(value:uint):void { _friction = value; }

		//------------------------------
		//   コンストラクタ
		//------------------------------
		public function ScrollOver(mc:DisplayObjectContainer, length:uint, vertical:Boolean = false):void
		{
			scrollContainer = mc;
			scrollLength = length;
			vertical = vertical;
		}

		//------------------------------
		//   スクロールモーション
		//------------------------------
		private function onEnter(e:Event):void
		{
			var percent:Number;
			var pos:Number;

			if (vertical == false && scrollLength < scrollContainer.width)
			{
				percent = scrollContainer.stage.mouseX / (scrollLength - 1);
				pos = (scrollLength - (scrollContainer.width + _space) - _space) * percent;
				scrollContainer.x = scrollContainer.x - ((scrollContainer.x - _space - pos) / _friction);
			}
			else if(vertical == true && scrollLength < scrollContainer.height)
			{
				percent = scrollContainer.stage.mouseY / (scrollLength - 1);
				pos = (scrollLength - (scrollContainer.height + _space) - _space) * percent;
				scrollContainer.y = scrollContainer.y - ((scrollContainer.y - _space - pos) / _friction);
			}
		}

		//------------------------------
		//   スタート
		//------------------------------
		public function start():void
		{
			scrollContainer.addEventListener(Event.ENTER_FRAME, onEnter);
		}

		//------------------------------
		//   ストップ
		//------------------------------
		public function stop():void
		{
			scrollContainer.removeEventListener(Event.ENTER_FRAME, onEnter);
		}

		//------------------------------
		//   ポジションリセット
		//------------------------------
		public function positionReset():void
		{
			scrollContainer.x = _space;
		}
	}
}

ドキュメントクラスはこんな感じ。インスタンス名がboxというmcをステージに配置しておく。boxの中身は写真を横にずらっと配置させる。縦スクロールに対応させるにはnew ScrollOver()の第3引数をtrueにする。frictionは摩擦具合。spaceはスクロールの両サイド間隔。start()メソッドでモーション開始。

Main.as

package
{
	import flash.display.Sprite;
	import flash.display.MovieClip;

	import info.five.ui.ScrollOver;

	public class Main extends Sprite
	{
		public function Main():void
		{
			//-----[横スクロールの場合]
			var so:ScrollOver = new ScrollOver(box, stage.stageWidth);

			//-----[縦スクロールの場合]
			//var so:ScrollOver = new ScrollOver(box, stage.stageHeight, true);
			so.friction = 20;
			so.space = 20;
			so.start();
		}
	}
}
2008.10.07
category
comments

TextFieldにフォーカスイベントを実装するには

テキストボックスにフォーカスが合った時のイベントの取り方。TextEventではなく、FocusEventを使うと実装できる。もっと早く理解できてたら良かったのに。

txt.addEventListener(FocusEvent.FOCUS_IN, focusInHandler);
txt.addEventListener(FocusEvent.FOCUS_OUT, focusOutHandler);

var guide:String = "please input your name";
var fmt:TextFormat = new TextFormat();

fmt.color = 0xDDDDDD;
txt.text = guide;
txt.setTextFormat(fmt);

//-----[フォーカスイン]
function focusInHandler(e:FocusEvent):void
{
	if(txt.text == guide)
	{
		txt.text = "";
	}
}

//-----[フォーカスアウト]
function focusOutHandler(e:FocusEvent):void
{
	if(txt.text == "")
	{
		txt.text = guide;
		txt.setTextFormat(fmt);
	}
}
2008.04.10
category
tag
comments

REMOVEDとREMOVED_FROM_STAGEの違い

2つのイベントの違いで困惑したのでメモ。

mc.addEventListener(Event.REMOVED, onRemoved);

自身、もしくは内包している子供のDisplayObjectがremoveChild()された時。

mc.addEventListener(Event.REMOVED_FROM_STAGE, onRemovedFromStage);

自身がremoveChild()された時。タイミングとしてはREMOVED_FROM_STAGEの前に REMOVEDイベントが発生する。

page 1 / 11