javascriptでHTML-WYSIWYGエディタを作ろうとしたときのメモを公開します。
1. 編集 - document.designMode
documentオブジェクトにはdesignModeというプロパティがあります。
このプロパティをonにすると、そのdocumentは編集可能になります。
2. 装飾 - document.execCommand()
あるボタンを押すと文字の色が変わる、などの制御を入れたい場合には、 メソッドexecCommandを使用すると便利です。
- 太字にする
- 文字の色を赤くする
execCommandの第一引数には、文字列でコマンドを指定します。
コマンドによっては第三引数の、パラメータが必要になります。
コマンドの一覧は以下に記載されています。 ( operaとsafariの資料はどこにあるのでしょう? )
Firefoxは、英語ドキュメントの方が、より多くのコマンドが載っています。
- IE コマンド一覧(英語) http://msdn2.microsoft.com/en-us/library/ms533049.aspx
- Firefox コマンド一覧(日本語) http://www.mozilla-japan.org/editor/midas-spec.html
- Firefox コマンド一覧(英語) http://www.mozilla.org/editor/midas-spec.html
3. 生成されるHTMLの違い
execCommandを使用して生成されるHTMLは、ブラウザによって違います。
この違いから、あるブラウザで生成したHTMLを(サーバ上で保持させ)、別のブラウザで
編集しようとすると、execCommandが期待通りに動いてくれないときがあります。
クロスブラウザの環境をサポートしたい場合には、この違いを吸収してあげなくてはなりません。
| command | Internet Explorer 6 | Firefox 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> | Hello | Hello | <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に定義されていない処理を行いたい場合など、何かと便利なオブジェクトです。
- IE TextRange(英語) http://msdn2.microsoft.com/en-us/library/ms535872.aspx
- Firefox Range(日本語) http://developer.mozilla.org/ja/docs/DOM:range
- Firefox Range(英語) http://developer.mozilla.org/en/docs/DOM:range
document上の現在のカーソル位置、もしくは、選択されている領域の、Rangeを取得します。
この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関係ないけど =)
とりあえずここまで。
最後に。
間違ってたらごめんなさい。