何を言っているの?
まずはこの画像を見て欲しい。
これどうやってるの!?って興味がある人向けの投稿です。
どういうことなのか?
動機
これをみてウォースゲーと思ったんだけど、日本語使えないじゃんってちょっと残念な感じになった。
ないならつくればいいじゃない。
ストラテジー
inputの上に一文字ごとのspanが表示されているという単純なもの。
そしてinputがcolor: transparent;に指定されているのが味噌か。
レガシーブラウザーの時はcolorを指定してJavaScriptを止めればいいだけだからエコだとも感心した。
まんま同じ構想でいけるかもと思った。
基本構造
<div class="special-text-input">
<div class="inner">
<input type="text"/>
<div class="display">
<!-- ここにspanを動的につけるよ -->
</div>
</div>
</div>
わりとピクセルで指定をしちゃった方が楽だけど、流行りのレスポンシブデザインまで視野に入れるならブロック要素として解釈した方が後々いいよね。
本家はinputのcolor: transparent;にしてキャレットまで作り込んでいたけど
背景色と同じ色にしてエコをはかった。デフォルトのUIのユーザービリティレベルまで作り込むのは大変だしね!
@import "compass";
@import "compass/reset";
$mimic-color: #fff;
@mixin user-select($value) {
-moz-user-select: $value;
-khtml-user-select: $value;
-webkit-user-select: $value;
user-select: $value;
}
@mixin unselectable {
pointer-events: none;
@include user-select(none);
}
$font-size: 14px;
$line-height: 1.5;
$min-height: $font-size * $line-height;
@mixin text-style {
font-size: $font-size;
font-family: serif;
line-height: $line-height;
letter-spacing: 1;
vertical-align: baseline;
}
.special-text-input {
display: block; /* 横目いっぱい: 親要素に幅を譲る */
/* テキストフィールドっぽいスタイルにする */
border: 1px solid #999;
background: $mimic-color;
@include box-shadow(rgba(0, 0, 0, .4) 1px 1px 3px);
> .inner {
position: relative;
display: block;
width: auto;
height: auto;
min-height: $min-height; /* 文字がないときに一文字分の高さをとる */
margin: 5px;
}
input, .display {
/* 大きさをぴったり合わせる */
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
input {
/* inputのデフォルトスタイルとる */
@include appearance(none);
@include box-shadow(none);
outline: none;
border: none;
// color: $mimic-color;
color: red; /* 今回はとりあえず見やすいように色つけておく */
@include text-style;
}
.display {
font-size: 0px; /* white-spaceの幅を消す */
@include unselectable; /* ユーザービリティ */
}
.display > span {
@include inline-block; // アニメーションがしたい!
@include text-style;
/* 選択負荷にする */
@include unselectable;
}
}
ここまでで、ダミーのspanを配置してみたり、inputにvalueを入れてみたりしてずれがないか、さまざまな横幅で試してみたりした。
次にJavaScriptでinputが入力されたときに.displayのspanの出力が同期されるようなscriptを書く。
# require jQuery / underscore
jQuery ->
@update_display = _.throttle( (target) =>
source = $(target).val()
@$display = $(target).closest(".special-text-input").find(".display")
str = source.split("")
if str.length <= 0
@$display.find("> span").html("")
else
@$display.find("> span:gt(#{str.length-1})").remove()
_.each( str, (char, index) =>
char = " " if /\s/.test char
$span = @$display.find("> span").eq(index)
if $span.length > 0
if $span.text() == char
return
else
$span.remove()
$attach = $("<span class=\"move\" unselectable=\"on\">#{char}</span>")
if @$display.find("> span").length > 0
if index == 0
@$display.prepend( $attach )
else
@$display.find("> span").eq(index-1).after( $attach )
else
@$display.prepend( $attach )
)
, 60 )
setInterval =>
$(".special-text-input input").each (index, element) =>
@update_display( element )
, 60
$(document).on "change keydown keyup", ".special-text-input input", (e) =>
@update_display( e.currentTarget )
もっと軽量化やプラグインぽい書き方はできるけれど、それは別の機会に。
アニメーション
本家はtransitionで動かしているけれど、アニメーションの仕方なんかはいろいろ好みがあるし、モバイルでもブリブリ動かしたいからCSSアニメーションをつけられるようにした。アニメーションのCSSだけ書き直したら再利用できるし!
@mixin keyframes($name){
@-webkit-keyframes #{$name} {
@content;
}
@-moz-keyframes #{$name} {
@content;
}
@-ms-keyframes #{$name} {
@content;
}
@keyframes #{$name} {
@content;
}
}
@include keyframes(move) {
from {
@include opacity(0);
@include transform( translate(100px, 0px) scale(10) rotate(90deg) );
}
to {
@include opacity(1);
@include transform( translate(0px, 0px) scale(1) rotate(0deg) );
}
}
.display > span {
&.move {
@include transform-style(preserve-3d);
@include experimental(animation-name, move);
@include experimental(animation-duration, 0.2s);
@include experimental(animation-timing-function, cubic-bezier(0.770, 0.000, 0.175, 1.000) );
}
}
できたー!
あとがき
思いのほかコードにボリュームが出てしまいました。反省。
今回書いたコードはgithubにあげておきました。
プラグイン化までの道のり
- text_field(area) x span の sync だけを切り出したプラグイン
- 今回の記述をスマートにします
お待たせしてごめんなさい。もう少しかかります。