« Javascript 安全なコンストラクタ | メイン | プロジェクト管理サービス »

WYSIWYGエディタに夢中になったときのメモ

javascriptでHTML-WYSIWYGエディタを作ろうとしたときのメモを公開します。



1. 編集 - document.designMode

documentオブジェクトにはdesignModeというプロパティがあります。
このプロパティをonにすると、そのdocumentは編集可能になります。

iframe内のdocumentを編集可能するにはこのようにします。
var doc, iframe = document.getElementById('xxx'); // IE, Operaの場合 doc = iframe.contentWindow.document; // FF, Safariの場合 doc = iframe.contentDocument; doc.designMode = 'on';


2. 装飾 - document.execCommand()

あるボタンを押すと文字の色が変わる、などの制御を入れたい場合には、 メソッドexecCommandを使用すると便利です。

  • 太字にする
  • document.execCommand( "bold", false, null );
  • 文字の色を赤くする
  • document.execCommand( "forecolor", false, 'red' );

execCommandの第一引数には、文字列でコマンドを指定します。
コマンドによっては第三引数の、パラメータが必要になります。

コマンドの一覧は以下に記載されています。 ( operaとsafariの資料はどこにあるのでしょう? )
Firefoxは、英語ドキュメントの方が、より多くのコマンドが載っています。



3. 生成されるHTMLの違い

execCommandを使用して生成されるHTMLは、ブラウザによって違います。

この違いから、あるブラウザで生成したHTMLを(サーバ上で保持させ)、別のブラウザで 編集しようとすると、execCommandが期待通りに動いてくれないときがあります。
クロスブラウザの環境をサポートしたい場合には、この違いを吸収してあげなくてはなりません。

生成されるHTMLを、手元の環境で確認した結果をまとめてみました。 (少し見づらいですが)
Table Navigation:
commandInternet Explorer 6Firefox 2.0 (styleWithCSS=false)Firefox 2.0 (styleWithCSS=true)Safari 3.0.3 (windows版)Opera 9.23
backcolor<FONT style="BACKGROUND-COLOR: red">Hello</FONT>HelloHello<span class="Apple-style-span" style="background-color: red;">Hello</span>Hello
bold<STRONG>Hello</STRONG><b>Hello</b><span style="font-weight: bold;">Hello</span><span class="Apple-style-span" style="font-weight: bold;">Hello</span><STRONG>Hello</STRONG>
createlink<A href="http://www.google.co.jp">Hello</A><a href="http://www.google.co.jp">Hello</a><a href="http://www.google.co.jp">Hello</a><a href="http://www.google.co.jp">Hello</a><A href="http://www.google.co.jp">Hello</A>
fontname<FONT face=sans-serif>Hello</FONT><font face="sans-serif">Hello</font><span style="font-family: sans-serif;">Hello</span><span class="Apple-style-span" style="font-family: sans-serif;">Hello</span><FONT face="sans-serif">Hello</FONT>
fontsize<FONT size=2>Hello</FONT><font size="2">Hello</font><font size="2">Hello</font><span class="Apple-style-span" style="font-size: small;">Hello</span><FONT size="2">Hello</FONT>
forecolor<FONT color=red>Hello</FONT><font color="red">Hello</font><span style="color: red;">Hello</span><span class="Apple-style-span" style="color: red;">Hello</span><FONT color="#ff0000">Hello</FONT>
formatblock<H1>Hello</H1><h1>Hello</h1><h1>Hello</h1><h1>Hello<br></h1><H1>Hello</H1>
indent<BLOCKQUOTE dir=ltr style="MARGIN-RIGHT: 0px"> <P>Hello</P></BLOCKQUOTE><blockquote>Hello</blockquote><div style="margin-left: 40px;">Hello</div><blockquote class="webkit-indent-blockquote">Hello<br></blockquote><BLOCKQUOTE>Hello</BLOCKQUOTE>
inserthorizontalrule<HR><hr size="2" width="100%"><hr style="width: 100%; height: 2px;"><hr id="undefined"></hr><HR>
insertimage<IMG src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><img src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><img src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><img src="http://www.google.co.jp/intl/ja_jp/images/logo.gif"><br><IMG src="http://www.google.co.jp/intl/ja_jp/images/logo.gif">
insertorderedlist<OL> <LI>Hello</LI></OL><ol><li>Hello</li></ol><ol><li>Hello</li></ol><ol id="undefined"><li>Hello<br></li></ol><OL><LI>Hello</LI></OL>
insertunorderedlist<UL> <LI>Hello</LI></UL><ul><li>Hello</li></ul><ul><li>Hello</li></ul><ul id="undefined"><li>Hello<br></li></ul><UL><LI>Hello</LI></UL>
italic<EM>Hello</EM><i>Hello</i><span style="font-style: italic;">Hello</span><span class="Apple-style-span" style="font-style: italic;">Hello</span><I>Hello</I>
justifycenter<P align=center>Hello</P><div align="center">Hello</div><div style="text-align: center;">Hello</div><DIV align="center">Hello</DIV>
justifyfull<P align=justify>Hello</P><div align="justify">Hello</div><div style="text-align: justify;">Hello</div><DIV align="justify">Hello</DIV>
justifyleft<P align=left>Hello</P><div align="left">Hello</div><div style="text-align: left;">Hello</div><DIV align="left">Hello</DIV>
justifyright<P align=right>Hello</P><div align="right">Hello</div><div style="text-align: right;">Hello</div><DIV align="right">Hello</DIV>
strikethrough<STRIKE>Hello</STRIKE><strike>Hello</strike><span style="text-decoration: line-through;">Hello</span><span class="Apple-style-span" style="text-decoration: line-through;">Hello</span><STRIKE>Hello</STRIKE>
subscript<SUB>Hello</SUB><sub>Hello</sub><sub>Hello</sub><span class="Apple-style-span" style="vertical-align: sub;">Hello</span><SUB>Hello</SUB>
superscript<SUP>Hello</SUP><sup>Hello</sup><sup>Hello</sup><span class="Apple-style-span" style="vertical-align: super;">Hello</span><SUP>Hello</SUP>
underline<U>Hello</U><u>Hello</u><span style="text-decoration: underline;">Hello</span><span class="Apple-style-span" style="text-decoration: underline;">Hello</span><U>Hello</U>

Firefoxでは、 execCommand で styleWithCSS を false にしてあげると、生成されるHTMLが、IE,Operaのものにより近くなります。
デフォルトはtrueです。



4. 改行の違い

IE6で改行を入力すると、なぜか2行分改行されているように見えます。
これは、改行入力時には、<br>タグではなく、<p></p>タグが新たに挿入されるためです。


ちなみに、<p>タグ内での改行では<p>が挿入されますし、<div>タグ内の改行なら<div>、<h1>タグ内なら<h1>といった具合に、カーソル位置の親のブロックエレメントタグが挿入されるようです。
また、Shiftキーを押しながらEnterキーを押すと、<br>タグを挿入できます。

幾つか対策方法を探してみました。

  • スタイルシートで、pタグのmargin-top と margin-bottom に 0px を指定する。

    これで見た目上の問題はなくなります。

    p { margin-top: 0px, margin-left:0px }
  • 独自に<br>タグを挿入し、イベント伝播を停止する。

    どうしても<br>タグを使いたい場合には、この方法が良さそうです。
    下記のfunctionで、keydownイベントをリスンしておくようにします。

    // IE Only function keydownHandler(e){ // Handle Enter Key if( e.keyCode == 13 && !e.shiftKey){ var range = document.selection.createRange(); range.pasteHTML("<br>"); e.returnValue = false; e.cancelBubble = true; } }



5. Range

最後に、Range (TextRange)オブジェクトを紹介します。 execCommandに定義されていない処理を行いたい場合など、何かと便利なオブジェクトです。


document上の現在のカーソル位置、もしくは、選択されている領域の、Rangeを取得します。

// IE var range = document.selection.createRange(); // IE以外 // 引数に0を指定しているので、先頭のRangeオブジェクトが取得されます。 var range = document.getSelection().getRangeAt(0); // IE以外でiframeから取得する場合 var range = iframe.contentWindow.getSelection().getRangeAt(0);


このRange(TextRange)オブジェクトを使うと色々なことができます。
ここでは、クロスブラウザを意識した操作を幾つか紹介します。


  • rangeの中に含まれるエレメントに対して共通の親エレメントを取得します。
    // IE var parent = range.parentElement(); // IE以外 var parent = range.commonAncestorContainer;
  • range同士の位置関係を比較します。
    // IE // targetRange が baserangeより右 (baseRange.compareEndPoints('StartToStart', targetRange) <=0) // targetRange が baserangeより左 (baseRange.compareEndPoints('EndToEnd', targetRange) >= 0) // IE以外 // targetRange が baserangeより右 (baseRange.compareBoundaryPoints(Range.START_TO_START, targetRange) <=0) // targetRange が baserangeより左 (baseRange.compareBoundaryPoints(Range.END_TO_END, targetRange) >=0)
  • rangeの始点、終点を、特定のエレメントの位置に移動させます。
    // IE var startElm = document.getElementById('elm1'); var endElm = document.getElementById('elm2'); var dstRange = document.body.createTextRange(); dstRange.moveToElementText(startElm); range.setEndPoint( 'StartToStart', dstRange); dstRange.moveToElementText(endElm); range.setEndPoint( 'EndToEnd', dstRange ); // IE以外 var startElm = document.getElementById('elm1'); var endElm = document.getElementById('elm2'); range.setStartBefore(startElm); range.setEndAfter(endElm);
  • rangeからHTML文字列を取得します。
    // IE var html = range.htmlText; // IE以外 var df = range.cloneContents(); var doc = range.startContainer.ownerDocument; var div = doc.createElement('div'); div.appendChild(df); var html = div.innerHTML;
  • rangeの位置にHTML文字列を挿入します。
    // IE var html = '<p>Hello</p>'; range.pasteHTML( html ); // IE以外 var html = '<p>Hello</p>'; document.execCommand('inserthtml', false, html); // これrange関係ないけど =)

とりあえずここまで。

最後に。
間違ってたらごめんなさい。

トラックバック

このエントリーのトラックバックURL:
http://www.fourmeisters.com/mt/mt-tb.cgi/83

コメントを投稿

(いままで、ここでコメントしたことがないときは、コメントを表示する前にこのブログのオーナーの承認が必要になることがあります。承認されるまではコメントは表示されません。そのときはしばらく待ってください。)

About

2007年09月14日 01:13に投稿されたエントリーのページです。

ひとつ前の投稿は「Javascript 安全なコンストラクタ」です。

次の投稿は「プロジェクト管理サービス」です。

他にも多くのエントリーがあります。メインページアーカイブページも見てください。

Powered by
Movable Type 3.35