まだ重たいCMSをお使いですか?
毎秒1000リクエスト を捌く超高速CMS「adiary

2019/07/05(金)JavaScriptから .png 画像を加工してCSSに適用する

png画像をJavaScript上で加工して、CSSに適用する方法。

背景

adiaryでは、UIアイコン(このサイトだとページ送りやタグ一覧に出ている▼等)を色指定に基づいて動的にロードしています。

仕組みとしては、予め128種類の色の異なる画像アイコンを用意して、指定色にもっとも近い色をロードするという感じになっていますが、いくつか問題があります。

  • 画像ファイルを増やすには限界があり近似色になってしまう。
  • 1KBにも満たないような小さなファイルをわざわざ別ファイルとしてロードさせるのは嫌。*1

色が固定で良いなら、base64で埋め込むだけの話なのですが……。

.ui-icon {
	background-image:	url('data:image/png;base64,XXXX----XXX=');
}

adiaryはなんとテーマの色をユーザーが自由に変更できるという素敵な機能が付いておりまして、そういうわけにも行きません……。

*1 : Webの表示速度を上げるためにも、ロードファイル数は極力少ないほうが良いわけです。

正攻法

続きを読む

2019/07/02(火)ES2015(ES6)をminifyしたいけど、Node.jsに興味はない場合

ES2015(ES6)のJavaScriptを minify(compress) したかったときに苦戦したメモ。

JavaScript Compressor

古くから有名でお手軽なYUI Compressorをインストールして、圧縮してみたのですが、謎のエラーが出て困ってしまいました。

Exception in thread "main" java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at com.yahoo.platform.yui.compressor.Bootstrap.main(Bootstrap.java:21)
Caused by: java.util.MissingResourceException: Can't find bundle for base name org.mozilla.javascript.resources.Messages, locale ja_JP
(以下略)

どうやら調べてみると、YUI CompressorはES6(let/const)に対応していないようです。

2019年にJavaScriptを書くのに「ES2015ではないスクリプトを書く」のは考えられないので、ES2015対応のJavaScript Compressorを探してみました。

Node.js に用はない

色々調べてみてもNode.js関連情報しか出てきません。

「Node.js」に興味はなく、純粋にJavaScriptを圧縮したいだけなのに……。

路頭に迷っていたのですが、Uglify-jsというNode.js関連ツールを使うことで、Node.jsとは関係なく単にJavaScriptを圧縮できることがわかりました。

Wabpackというツールもあるのですが、軽く調べた感じではNode.js専用ツールっぽい感じで、単なるJavaScriptの圧縮ツールとしては使いにくそうでした。

Uglify-es

残念ながらUglify-jsは「Uglifyjs 3 (Ver3)」でもES6には対応していません。超注意点です。

Uglify-jsからフォークされた「Uglify-es」というツールを使います。まずこのツールを入れるために、Node.js用のパッケージマネージャー npm をインストールする必要があります。

今回インストールしたサーバはDebainですが、普通にapt-getすると古いツールが入るため、「Debian9 (stretch) に Node.js をインストールする」を参考にVer10系の Node.js を導入しました。

curl -sL https://deb.nodesource.com/setup_10.x | bash -
apt-get install -y nodejs

これで npm が使用できるようになったので、Uglify-esを導入します。

npm install uglify-es

Makefile

Uglify-js@2、Uglify-js@3、Uglify-esでみんなコマンドラインオプションが違うので、情報を探す際はご注意ください。

参考までに作成したMakefileを晒しておきます。

#
# Makefile for adiary.min.js
#
COMPRESSOR = uglifyjs
OPTIONS    = -c -m --comments '/^!/' --source-map includeSources,filename

OUTPUT	   = ../adiary.min.js
MAP_FILE   = ../adiary.min.map

FILES = \
	01_global-variables.js	\
	10_jquery-ext.js	\
	20_init.js		\
	30_css.js		\
	90_PrefixStorage.js	\
	91_jquery-storage.js

all:  $(OUTPUT)

$(OUTPUT): $(FILES) Makefile
	$(COMPRESSOR) $(OPTIONS) $(FILES) -o $(OUTPUT)
	mv $(OUTPUT).map $(MAP_FILE)
-c
ソースファイルを圧縮します
-m
Mangleします。これをつけたほうが圧縮率が高くなります。
--source-map includeSources,filename
mapファイルを作成します。
--comments '/^!/'
「!」マークで始まるコメント(ライセンスコメント)を残します。

2015/05/14(木)スマホでドラッグ&ドロップのエミュレーション

jQuery UIdynatree を使用していて、ドラッグアンドドロップ操作が必須であるにも関わらずスマホでは何もできないので、汎用的な実装をjQuery pluginで実現しました。

タッチ操作でDnDをエミュレーションする

タッチパネル系イベントは独特らしく、短くタップしたときのみ mousedown や click 等のマウスイベントが発生してくれますが、長く触っているとマウスイベントは発生しないようです。

ですので、以下のように実装しました。

  • touchstart で mousedown を発火。
  • touchmove で mousemove を発火し、mouseleave と mouseenter をエミュレーション。
  • touchend で mouseup を発火。

mouseover, mouseoutも実装はできますが無視しました。jQuery UIが問題なく動く程度には実装しているつもりです。

エミュレーションがonの状態で短くタップすると、mousedown/mouseupイベントが2重に発生する可能性がありますが、解決策がないので保留です。

ソース

修正BSDライセンスとします。jQuery pluginですので適当に読み込ませて次のように使ってください。

$(dom).dndEmulation();

以下のソースは最新でない可能性があります。最新版は、adiaryのサイトからGitHub経由で「js/adiary.js」を参照して該当部のみ抜粋してください。該当部のみ抜粋する限り修正BSDライセンスで扱って構いません。

var TouchDnDTime  = 700;
$.fn.extend({
//////////////////////////////////////////////////////////////////////////////
// Copyright (C)2015 nabe@abk, New BSD License.
//////////////////////////////////////////////////////////////////////////////
dndEmulation: function(){
	var self = this[0];
	if (!self) return;

	// mouseイベント作成
	function make_mouse_event(name, evt, touch) {
		var e = $.Event(name);
		e.altKey   = evt.altKey;
		e.metaKey  = evt.metaKey;
		e.ctrlKey  = evt.ctrlKey;
		e.shiftKey = evt.shiftKey;
		e.clientX = touch.clientX;
		e.clientY = touch.clientY;
		e.screenX = touch.screenX;
		e.screenY = touch.screenY;
		e.pageX   = touch.pageX;
		e.pageY   = touch.pageY;
		e.which   = 1;
		return e;
	}
	// 自分自身を含めた親要素をすべて取得
	function get_par_elements(dom) {
		var ary  = [];
		while(dom) {
			ary.push( dom );
			if (dom == self) break;
			dom = dom.parentNode;
		}
		return ary;
	}

	// クロージャ変数
	var prev;
	var flag;

	// mousedownエミュレーション
	this.bind('touchstart', function(_evt){
		var evt = _evt.originalEvent;
		prev = evt.target;
		var e = make_mouse_event('mousedown', evt, evt.touches[0]);
		$( prev ).trigger(e);
		
		// ある程度時間が経過しないときは処理を無効化する。
		flag = false;
		setTimeout(function(){
			flag=true;
		}, TouchDnDTime)
	});

	// mouseupエミュレーション
	this.bind('touchend', function(_evt){
		var evt = _evt.originalEvent;
		var e = make_mouse_event('mouseup', evt, evt.changedTouches[0]);
		$( evt.target ).trigger(e);
	});

	// ドラッグエミュレーション
	this.bind('touchmove', function(_evt){
		var evt = _evt.originalEvent;

		// 一定時間立たなければ、処理を開始しない
		if (!flag) return;

		var touch = evt.changedTouches[0];
		var dom   = document.elementFromPoint(touch.clientX, touch.clientY);
		var enter = get_par_elements(dom);

		// マウス移動イベント
		var e = make_mouse_event('mousemove', evt, touch);
		$(enter).trigger(e);

		// 要素移動がなければこれで終了
		evt.preventDefault();
		if (dom == prev) return;

		// 要素移動があれば leave と enter イベント生成
		var leave = get_par_elements(prev);

		// 重複要素を除去
		while(leave.length && enter.length
		   && leave[leave.length -1] == enter[enter.length -1]) {
			leave.pop();
			enter.pop();
		}

		// イベント発火
		var e_leave = make_mouse_event('mouseleave', evt, touch);
		var e_enter = make_mouse_event('mouseenter', evt, touch);
		$(leave).trigger( e_leave );
		$(leave).trigger( e_enter );

		// 新しい要素を保存
		prev=dom;
	});
}
//////////////////////////////////////////////////////////////////////////////
});

2015/05/13(水)Lightbox2 にスワイプ対応パッチを充てた

充てたというかパッチしました。作りました。jQueryのみです。

画像をめくるときにタッチパネルのスワイプ操作でも反応するようになっています。特にアニメーションはしません。

jQueryのバグなのか「touchstart」「touchmove」イベントを全く拾わないので、「window.addEventListener」に逃げました。もしもっとスマートな解決方法を見つけたら教えてください。

これを使用しているadiaryもよろしくお願いします

その他の変更点

#lightbox-min-width {
	display:		none;
	width:			300px;
}

と設定すると、横幅が小さすぎる画像を表示するとき、指定したピクセルまでアスペクト比を保持して拡大して表示します。

極端に縦長の画像の場合はアスペクト比を保持できて、画面に収まる範囲内で拡大されます。

2012/08/22(水)input type="text"等の自由リサイズスクリプト

Google ChromeやFirefoxには標準で<textarea>の自由リサイズ機能(マウスでのリサイズ機能)がありますが、input type="text"等のテキストボックスにはその機能がありません。

jQueryで実装してみました。

使い方

以下のCSSとJavaScriptを導入してください。あとは自動的に機能が導入されます。

  • jQueryを先にロードしてください。必要バージョンは分かりません(苦笑)
  • Google ChromeとFirefoxで動作確認しています。
  • CSSの width が掴める部分のサイズになります。
  • CSSの z-index の下3桁(1000で割った余り)がテキストボックスの最小サイズになります。*1
  • CSSの display:none を有効にするとフォームリサイズ機能を無効にできます。
    • テキストボックスの初期サイズの方が小さい場合はそちらが最小サイズになります。
  • IE6,7で動作しましたが、IE8,9では動作しませんでした(動作しないだけで不具合はありません)。
    • 手前にあるspanよりも裏側にあるテキストボックスが優先される謎の仕様のせいです。

続きを読む

OK キャンセル 確認 その他