コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

JavaScriptでテキストの装飾をしてみる

いろいろな理由があって、Googleドキュメントのようにリッチテキストをブラウザ上で行う方法を調べてみました。

一応、

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

http://www.fourmeisters.com/blog/yoshi/2007/09/wysiwyg.html

にあるように、document.designModeというものがあり、これで簡単にできそうな感じです。

ただし、今回は単純にマーキングとかアンダーラインだけを考慮したいので、テキスト自体の編集をされてしまうと困るので違う方法がないか探してみたところ、SelectionオブジェクトとRangeオブジェクトを使うことで実現することができました。
まず、RangeオブジェクトはDOMインターフェースとして定義されています。

Document Object Model Range

http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html

Selectionオブジェクトを取得する方法の定義やrangeの日本語訳等は以下のサイトを参考にしました。
https://developer.mozilla.org/ja/DOM/Selection
https://developer.mozilla.org/ja/DOM/range

実際に使用したメソッド等を簡単に説明します。
まず、通常のWebページに置いて画面上選択されている部分を取得するためには、Selectionオブジェクトを取得する必要があります。Selectionオブジェクトは、以下のように取得します。Chromeではdocument.getSelection()でも取得できましたが、Firefoxでは取得できなかったためwindowからgetSelectionしました。

var selection = window.getSelection();

そして、選択範囲を取得するためにはSelectionオブジェクトからさらにRangeオブジェクトを取得する必要があります。FirefoxのようにCtrlキーを押しながら複数箇所を選択できるブラウザもあるため、Rangeオブジェクトはそのインデックス(何番目の選択範囲か)を指定して取得します。具体的には以下の様になります。

var range = selection.getRangeAt(index);

取得したrangeの内容は、以下のメソッドで複製を取得できます。

range.cloneContents()

また、選択している部分の内容を削除するためには、以下のメソッドを使用します。

range.deleteContents();

さらに、選択している部分にNodeを追加するには以下のメソッドを使用します。

range.insertNode(node);

これらのメソッドを使用して、簡単な装飾ツールを作成することができます。多少バグってますが、気にしない。ソースは以下。

<!DOCTYPE html>
<html>
 <head>
  <meta charset="utf-8">
  <title>テキストの装飾</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
  <script type="text/javascript">
$(function() {
    $('#button1').click(function() {
        designString('background-color: #ffff00;');
    });

    $('#button2').click(function() {
        designString('text-decoration: underline;');
    });
});

function designString(styleString) {
    var selection = window.getSelection();
    if (!selection || selection.rangeCount == 0) {
        return;
    }
    var range = selection.getRangeAt(0);

    var contentsId = 'div1';

    var start = $(range.startContainer).parents('#' + contentsId).attr('id') ==  contentsId ? true : false;
    var end = $(range.endContainer).parents('#' + contentsId).attr('id') == contentsId ? true : false;

    if (start && end) {
        var add = $('<span style="' + styleString + '"></span>').append(range.cloneContents());
        range.deleteContents();
        range.insertNode(add.get(0));
    }

    selection.collapseToStart();
}
  </script>
 </head>
 <body style="line-height: 1.5em;">
  <div id="div1">あいうえおかきくけこさしすせそたちつてとなにぬねの<br>
はひふへほまみむめもやゆよらりるれろわをん<br>
あいうえおかきくけこさしすせそたちつてとなにぬねの<br>
はひふへほまみむめもやゆよらりるれろわをん<br>
あいうえおかきくけこさしすせそたちつてとなにぬねの<br>
はひふへほまみむめもやゆよらりるれろわをん<br>
あいうえおかきくけこさしすせそたちつてとなにぬねの<br>
はひふへほまみむめもやゆよらりるれろわをん</div>
  <button id="button1">マーカー</button><br>
  <button id="button2">下線</button>
  <div id="result"></div>
 </body>
</html>

動く物は以下に置いてあります。一応、Firefox 3.6、Chrome 8、Safari 5では確認しています。
http://www.kuwalab.net/html5/range1.html
IEの対応等もできなくはないのですが、ここまでにしておきます。