実践linq.js リンクフェーダーの作り方

実践というか、普通に使えそうな例を出して販促活動しよう、と思ったので、linq.jsを使ってリンクフェーダーを作ってみました。リンクにマウスカーソルを乗せると色がフェードしながら変わるってものを作ります。とりあえずサンプル。このサイトで使用しているものそのままなので、#e10000に向かってフェードします。色の差が激しいもののほうが効果が分かりやすいと思います、ので、このサイトでは気付かないぐらい地味な変化です。気付かないぐらいが丁度良いという美学だそうです。

サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル
サンプルサンプルサンプル

神は細部に宿る。こういう、地味なところにも神経が行き届いていると、良く出来てるなあ、と私は思うのですがどうでしょう? 地味ではありますが、最も目に触れる部分でもあるので演出効果はわりと高いと思います。では、ソースを見ていきます。

// customize : here

var _targetColor = "#FFFFFF"; // #RRGGBB
var fadeSpeed = 400; // millisecond

// customize : end.

customize : here(笑) というしょうもない英語力が光る出だしに萎える……。_targetColorは目標色。普通はa:hoverと同色を用います。というか、このスクリプトを適用するとa:hoverのほうが無効化されます。fadeSpeedのほうはフェードにかかる時間で、10000とかにすると、とってもゆーっくりフェードします。1秒ですら意外とウザく感じるので500以下がお薦め。

// Global Variable

var ColorTable = { aliceblue: 'f0f8ff', antiquewhite: 'faebd7', aq...

var HexToDecTable = (function()
{
    var numbers = E.RangeTo(0, 9);
    var alphabet = E.RangeTo('a'.charCodeAt(0), 'f'.charCodeAt(0)).Select("String.fromCharCode($)");
    var digits = numbers.Concat(alphabet);
    var colors = E.From(digits).SelectMany(function(i)
    {
        return E.From(digits).Select(function(j)
        {
            return i.toString() + j.toString();
        })
    });
    return colors.Select("v,i=>{v:v,i:i}").ToObject("$.v", "$.i");
})();

var DecToHexTable = E.From(HexToDecTable).ToObject("$.Value", "$.Key");

var targetColor = new Color(_targetColor);
var resolution = 10;

全体で使う変数。変数っていうか定数です。constがあれば全部constにしたいですね、これらは。ColorTableは、色指定が色名で来た場合に16進数の文字列に変換するためのもの。IE以外だと10進数のRGBで来るんですけど、IEは色名で指定されていると、そのまま色名で返してくるのでこういうのが必要なんですね、UZEEEEE。

HexToDecTableは16進数の色表現を10進数の色表現に変換するためのテーブル。わざわざテーブル作らないで普通に16進->10進変換関数を用意すればいいだけの気もしますが、このテーブルを作らないとlinq.jsの出番がないのです。ていうか実はlinq.jsの出番はここだけだったりします。

中身は前回のサンプルとほとんど同じで、0-9とa-fを連結して一つにしたら、それの直積で全組み合わせを出して00-ffの256通りを作る。その後の「Select("v,i=>{v:v,i:i}").ToObject("$.v", "$.i")」は、v(value)が生成した16進数の表現で、i(index)が0から255までのインデックスなのですが、ちょうど10進数の表現になってます。それをToObject、つまりキーが16進数表現、値が10進数表現のオブジェクトに変換します。

画像で見るとこんな感じ。DecToHexTableは、HexToDecTableのキーと値をひっくり返したものです。ひっくり返す処理をたった一行でサクッと記述出来るのは、地味に便利。残る二つ、targetColorはまんまで、フェードの目標色です。Colorクラスはすぐ後で説明します。resolutionは解像度、フェードの滑らかさです。つまりはSetIntervalの更新間隔です(笑)

// Class

function Palette(dec, hex)
{
    this.Dec = dec;
    this.Hex = hex;
}

function Color(strColor)
{
    strColor = strColor.toLowerCase();
    if (ColorTable.hasOwnProperty(strColor)) strColor = '#' + ColorTable[strColor];

    if (strColor.indexOf("rgb") != -1)
    {
        var array = strColor.split(",");
        var decR = parseInt(array[0].substring(4, array[0].length));
        var decG = parseInt(array[1]);
        var decB = parseInt(array[2].substring(0, array[2].length - 1));
        this.R = new Palette(decR, DecToHexTable[decR]);
        this.G = new Palette(decG, DecToHexTable[decG]);
        this.B = new Palette(decB, DecToHexTable[decB]);
    }
    else
    {
        var hexR = strColor.substr(1, 2);
        var hexG = strColor.substr(3, 2);
        var hexB = strColor.substr(5, 2);
        this.R = new Palette(HexToDecTable[hexR], hexR);
        this.G = new Palette(HexToDecTable[hexG], hexG);
        this.B = new Palette(HexToDecTable[hexB], hexB);
    }
}

Color.prototype.SetFromDec = function(decR, decG, decB)
{
    this.R.Dec = decR;
    this.R.Hex = DecToHexTable[decR];
    this.G.Dec = decG;
    this.G.Hex = DecToHexTable[decG];
    this.B.Dec = decB;
    this.B.Hex = DecToHexTable[decB];
}

Color.prototype.ToHex = function()
{
    return "#" + this.R.Hex + this.G.Hex + this.B.Hex;
}

今回作ったクラスはPaletteとColorの二つ。Paletteは10進数のDecと16進数のHexを持つ、ただそれだけのもの。クラス名がPaletteで良いのかどうかが限りなく微妙で悩ましいです。ていうかかなりダメ。

ColorはR,G,Bという3つの独立したPaletteを持つクラスです。まずコンストラクタは、文字列でくる色表現をPaletteに作り替えます。で、まあ、分岐が3つほど、1つ目は、"aliceblue"とか、色名でやって来た場合にColorTableを使って16進表現に変換。2つ目は、rgb(10,10,10)とか、10進でくる(IE以外)色表現を分解します。substringの決め打ちですね。だせえ。3つ目は、16進でくる(IE)色表現をやっぱりsubstringの決め打ちで分解。なお、片割れはみたとおり、DecToHexTable,HexToDecTableのプロパティで直接振ってます。

SetFromDec関数は10進できた色からセット、ってまんまですがまんまです。ToHexは16進で表した文字列で出力。そのまんまですがそのまんまです。

// Method

function FadeText(elem, color, targetColor, intervalID)
{
    clearInterval(intervalID[0]);
    var fadeR = (targetColor.R.Dec - color.R.Dec) / (fadeSpeed / resolution);
    var fadeG = (targetColor.G.Dec - color.G.Dec) / (fadeSpeed / resolution);
    var fadeB = (targetColor.B.Dec - color.B.Dec) / (fadeSpeed / resolution);
    var r = color.R.Dec;
    var g = color.G.Dec;
    var b = color.B.Dec;

    intervalID[0] = setInterval(function()
    {
        r += fadeR;
        g += fadeG;
        b += fadeB;
        if ((fadeR > 0) ? r >= targetColor.R.Dec : r <= targetColor.R.Dec) r = targetColor.R.Dec;
        if ((fadeG > 0) ? g >= targetColor.G.Dec : g <= targetColor.G.Dec) g = targetColor.G.Dec;
        if ((fadeB > 0) ? b >= targetColor.B.Dec : b <= targetColor.B.Dec) b = targetColor.B.Dec;
        color.SetFromDec(Math.floor(r), Math.floor(g), Math.floor(b));
        elem.style.color = color.ToHex();
        if (r === targetColor.R.Dec && g === targetColor.G.Dec && b === targetColor.B.Dec)
        {
            clearInterval(intervalID[0]);
        }
    }, resolution);
}

実働するメソッドはこれだけー。この辺もう全然linq.js関係ないし、私のJavaScriptスキルっていうかプログラミングスキルの微妙さが浮き彫りになるだけの代物なので、是非Disって欲しい。そもそもHexって使ってないから必要ないぢゃん、というのには今気付いた。

function AttachEvent(elem, event, func)
{
    if (elem.addEventListener) elem.addEventListener(event, func, false);
    else if (elem.attachEvent) elem.attachEvent("on" + event, func);
}

function Initialize()
{
    var nodeList = document.getElementsByTagName("a");

    E.From(nodeList).ForEach(function(elem)
    {
        var strColor = (elem.currentStyle)
                ? elem.currentStyle.color
                : document.defaultView.getComputedStyle(elem, null).getPropertyValue("color");
        var color = new Color(strColor);
        var intervalID = []; // call by reference
        AttachEvent(elem, "mouseover", function() { FadeText(elem, color, targetColor, intervalID) });
        AttachEvent(elem, "mouseout", function() { FadeText(elem, color, new Color(strColor), intervalID) });
    });
}

// Main

AttachEvent(window, "load", Initialize);

これで全部。登録部分です。AttachEventは、一番手抜きな形式ということで。Initializeは、aタグに対してイベント登録してます。ここは少しだけlinq.js使ってます、ForEach便利、ForEach最高。SetIntervalで関数の引数は、こんなののためだけに無名関数を使うのはどうなんでしょうか、誰かベストな方法を教えてください。SetInterval用のIDも、参照渡しのためだけに配列にしてるのが、どーにもこーにもいかんですねえ。ていうかSetIntervalのIDってどうやって管理するのがベストなのかさっぱり分かりません。

最後に

ライセンスはMs-PL、つまり好きに使って好きに改造して好きに配布していいですよライセンスにします。このスクリプトはlinq.jsの次回更新時に同梱するつもりですが、今すぐ使いたいという奇特な人は、このサイトで使ってるものを使ってやってください。先頭のtargetColorの指定を自分のサイトにあうように変えて、linq.jsの後に読み込んでください。linqfaderっていうのは、勿論linkとlinqをかけてるわけです。動作確認はWindows VistaのIE7, Firefox3, Chromeで行いました。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2023

Twitter:@neuecc GitHub:neuecc

Archive