标签:
转载请注明: TheViper http://www.cnblogs.com/TheViper
为什么会有这个需求?
当我们需要一个文本输入框(编辑器),它的功能定位介于专门的富文本编辑器和裸(原生)文本框之间。这时,如果用专门富文本编辑器,如kindeditor,ueditor,显的很大材小用,而且这两个的体积都不小,而体积小的富文本编辑器又是针对现代浏览器的。
贴吧发帖和知乎发问题的编辑器就是典型的这种需求
问题的出现
下面是这个问题的呈现,ie8下,知乎编辑器中插入图片
首先将光标移到已经输入文字的任意位置,然后让光标在编辑器中失去焦点。上传图片,最后看到图片并没有在光标最后在编辑器中的位置。
如果没有失去焦点,或者浏览器是现代浏览器,则不存在这个问题。
网上的解决方案
网上有很多方案,但至少到现在,我没有看到一个成功的。一般问题都出在编辑器失去焦点后,不能“智能”的在光标原位置插入图片。
最接近的方案就是很简单的win.document.execCommand("insertImage", ‘‘,data);,但这个只能针对现代浏览器。
我的成功解决方案
先看下效果
firefox
ie8
注意到在kindeditor,ueditor中,都很好的解决了这个问题,那最简单的方法就是从源码中扒出那些代码。ueditor的源码复杂点,多点,就从kindeditor中扒。kindeditor的源码也就5000多行,还没jquery多.
kindeditor里面的思路是每次mouseup的时候,就重新锁定selection,range.
range的startContainer和endContainer一样,startOffset和endOffset也一样,也就是说创建的range实际上一个里面什么都没有的range,没有选中内容的range当然就是光标了。
self.afterChange(function(e) { cmd.selection(); }); ........ afterChange : function(fn) { var self = this, doc = self.doc, body = doc.body; K(doc).mouseup(fn); return self; }
这个就是最精华的地方,其他都没什么好说的,无非就是对selection,range的封装。
下面是从kindeditor中扒下的代码
1 function _extend(child, parent, proto) { 2 if (!proto) { 3 proto = parent; 4 parent = null; 5 } 6 var childProto; 7 if (parent) { 8 var fn = function () {}; 9 fn.prototype = parent.prototype; 10 childProto = new fn(); 11 _each(proto, function(key, val) { 12 childProto[key] = val; 13 }); 14 } else { 15 childProto = proto; 16 } 17 childProto.constructor = child; 18 child.prototype = childProto; 19 child.parent = parent ? parent.prototype : null; 20 } 21 function _iframeDoc(iframe) { 22 // iframe = _get(iframe); 23 return iframe.contentDocument || iframe.contentWindow.document; 24 } 25 var _IERANGE = !window.getSelection; 26 function _updateCollapsed(range) { 27 range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); 28 return range; 29 } 30 function _moveToElementText(range, el) { 31 try { 32 range.moveToElementText(el); 33 } catch(e) {} 34 } 35 function _getStartEnd(rng, isStart) { 36 var doc = rng.parentElement().ownerDocument, 37 pointRange = rng.duplicate(); 38 pointRange.collapse(isStart); 39 var parent = pointRange.parentElement(), 40 nodes = parent.childNodes; 41 if (nodes.length === 0) { 42 return {node: parent.parentNode, offset: K(parent).index()}; 43 } 44 var startNode = doc, startPos = 0, cmp = -1; 45 var testRange = rng.duplicate(); 46 _moveToElementText(testRange, parent); 47 for (var i = 0, len = nodes.length; i < len; i++) { 48 var node = nodes[i]; 49 cmp = testRange.compareEndPoints(‘StartToStart‘, pointRange); 50 if (cmp === 0) { 51 return {node: node.parentNode, offset: i}; 52 } 53 if (node.nodeType == 1) { 54 var nodeRange = rng.duplicate(), dummy, knode = K(node), newNode = node; 55 if (knode.isControl()) { 56 dummy = doc.createElement(‘span‘); 57 knode.after(dummy); 58 newNode = dummy; 59 startPos += knode.text().replace(/\r\n|\n|\r/g, ‘‘).length; 60 } 61 _moveToElementText(nodeRange, newNode); 62 testRange.setEndPoint(‘StartToEnd‘, nodeRange); 63 if (cmp > 0) { 64 startPos += nodeRange.text.replace(/\r\n|\n|\r/g, ‘‘).length; 65 } else { 66 startPos = 0; 67 } 68 if (dummy) { 69 K(dummy).remove(); 70 } 71 } else if (node.nodeType == 3) { 72 testRange.moveStart(‘character‘, node.nodeValue.length); 73 startPos += node.nodeValue.length; 74 } 75 if (cmp < 0) { 76 startNode = node; 77 } 78 } 79 if (cmp < 0 && startNode.nodeType == 1) { 80 return {node: parent, offset: K(parent.lastChild).index() + 1}; 81 } 82 if (cmp > 0) { 83 while (startNode.nextSibling && startNode.nodeType == 1) { 84 startNode = startNode.nextSibling; 85 } 86 } 87 testRange = rng.duplicate(); 88 _moveToElementText(testRange, parent); 89 testRange.setEndPoint(‘StartToEnd‘, pointRange); 90 startPos -= testRange.text.replace(/\r\n|\n|\r/g, ‘‘).length; 91 if (cmp > 0 && startNode.nodeType == 3) { 92 var prevNode = startNode.previousSibling; 93 while (prevNode && prevNode.nodeType == 3) { 94 startPos -= prevNode.nodeValue.length; 95 prevNode = prevNode.previousSibling; 96 } 97 } 98 return {node: startNode, offset: startPos}; 99 } 100 function _getEndRange(node, offset) { 101 var doc = node.ownerDocument || node, 102 range = doc.body.createTextRange(); 103 if (doc == node) { 104 range.collapse(true); 105 return range; 106 } 107 if (node.nodeType == 1 && node.childNodes.length > 0) { 108 var children = node.childNodes, isStart, child; 109 if (offset === 0) { 110 child = children[0]; 111 isStart = true; 112 } else { 113 child = children[offset - 1]; 114 isStart = false; 115 } 116 if (!child) { 117 return range; 118 } 119 if (K(child).name === ‘head‘) { 120 if (offset === 1) { 121 isStart = true; 122 } 123 if (offset === 2) { 124 isStart = false; 125 } 126 range.collapse(isStart); 127 return range; 128 } 129 if (child.nodeType == 1) { 130 var kchild = K(child), span; 131 if (kchild.isControl()) { 132 span = doc.createElement(‘span‘); 133 if (isStart) { 134 kchild.before(span); 135 } else { 136 kchild.after(span); 137 } 138 child = span; 139 } 140 _moveToElementText(range, child); 141 range.collapse(isStart); 142 if (span) { 143 K(span).remove(); 144 } 145 return range; 146 } 147 node = child; 148 offset = isStart ? 0 : child.nodeValue.length; 149 } 150 var dummy = doc.createElement(‘span‘); 151 K(node).before(dummy); 152 _moveToElementText(range, dummy); 153 range.moveStart(‘character‘, offset); 154 K(dummy).remove(); 155 return range; 156 } 157 function _toRange(rng) { 158 var doc, range; 159 if (_IERANGE) { 160 if (rng.item) { 161 doc = _getDoc(rng.item(0)); 162 range = new KRange(doc); 163 range.selectNode(rng.item(0)); 164 return range; 165 } 166 doc = rng.parentElement().ownerDocument; 167 var start = _getStartEnd(rng, true), 168 end = _getStartEnd(rng, false); 169 range = new KRange(doc); 170 range.setStart(start.node, start.offset); 171 range.setEnd(end.node, end.offset); 172 return range; 173 } 174 var startContainer = rng.startContainer; 175 doc = startContainer.ownerDocument || startContainer; 176 range = new KRange(doc); 177 range.setStart(startContainer, rng.startOffset); 178 range.setEnd(rng.endContainer, rng.endOffset); 179 return range; 180 } 181 function KRange(doc) { 182 this.init(doc); 183 } 184 _extend(KRange,{ 185 init : function(doc) { 186 var self = this; 187 self.startContainer = doc; 188 self.startOffset = 0; 189 self.endContainer = doc; 190 self.endOffset = 0; 191 self.collapsed = true; 192 self.doc = doc; 193 }, 194 setStart : function(node, offset) { 195 var self = this, doc = self.doc; 196 self.startContainer = node; 197 self.startOffset = offset; 198 if (self.endContainer === doc) { 199 self.endContainer = node; 200 self.endOffset = offset; 201 } 202 return _updateCollapsed(this); 203 }, 204 setEnd : function(node, offset) { 205 var self = this, doc = self.doc; 206 self.endContainer = node; 207 self.endOffset = offset; 208 if (self.startContainer === doc) { 209 self.startContainer = node; 210 self.startOffset = offset; 211 } 212 return _updateCollapsed(this); 213 }, 214 setStartBefore : function(node) { 215 return this.setStart(node.parentNode || this.doc, 0); 216 }, 217 setStartAfter : function(node) { 218 return this.setStart(node.parentNode || this.doc, K(node).index() + 1); 219 }, 220 setEndBefore : function(node) { 221 return this.setEnd(node.parentNode || this.doc, K(node).index()); 222 }, 223 setEndAfter : function(node) { 224 return this.setEnd(node.parentNode ||1); 225 }, 226 selectNode : function(node) { 227 return this.setStartBefore(node).setEndAfter(node); 228 }, 229 selectNodeContents : function(node) { 230 return this.setStart(node, 0).setEnd(node, 0); 231 }, 232 collapse : function(toStart) { 233 if (toStart) { 234 return this.setEnd(this.startContainer, this.startOffset); 235 } 236 return this.setStart(this.endContainer, this.endOffset); 237 }, 238 cloneRange : function() { 239 return new KRange(this.doc).setStart(this.startContainer, this.startOffset).setEnd(this.endContainer, this.endOffset); 240 }, 241 toString : function() { 242 var rng = this.get(), str = _IERANGE ? rng.text : rng.toString(); 243 return str.replace(/\r\n|\n|\r/g, ‘‘); 244 }, 245 insertNode : function(node) { 246 var self = this, 247 sc = self.startContainer, so = self.startOffset, 248 ec = self.endContainer, eo = self.endOffset, 249 firstChild, lastChild, c, nodeCount = 1; 250 if (sc.nodeType == 1) { 251 c = sc.childNodes[so]; 252 if (c) { 253 sc.insertBefore(node, c); 254 if (sc === ec) { 255 eo += nodeCount; 256 } 257 } else { 258 sc.appendChild(node); 259 } 260 } else if (sc.nodeType == 3) { 261 if (so === 0) { 262 sc.parentNode.insertBefore(node, sc); 263 if (sc.parentNode === ec) { 264 eo += nodeCount; 265 } 266 } else if (so >= sc.nodeValue.length) { 267 if (sc.nextSibling) { 268 sc.parentNode.insertBefore(node, sc.nextSibling); 269 } else { 270 sc.parentNode.appendChild(node); 271 } 272 } else { 273 if (so > 0) { 274 c = sc.splitText(so); 275 } else { 276 c = sc; 277 } 278 sc.parentNode.insertBefore(node, c); 279 if (sc === ec) { 280 ec = c; 281 eo -= so; 282 } 283 } 284 } 285 if (firstChild) { 286 self.setStartBefore(firstChild).setEndAfter(lastChild); 287 } else { 288 self.selectNode(node); 289 } 290 return self.setEnd(ec, eo); 291 }, 292 isControl : function() { 293 var self = this, 294 sc = self.startContainer, so = self.startOffset, 295 ec = self.endContainer, eo = self.endOffset, rng; 296 return sc.nodeType == 1 && sc === ec && so + 1 === eo && K(sc.childNodes[so]).isControl(); 297 }, 298 shrink : function() { 299 var self = this, child, collapsed = self.collapsed; 300 while (self.startContainer.nodeType == 1 && (child = self.startContainer.childNodes[self.startOffset]) && child.nodeType == 1 && !K(child).isSingle()) { 301 self.setStart(child, 0); 302 } 303 if (collapsed) { 304 return self.collapse(collapsed); 305 } 306 while (self.endContainer.nodeType == 1 && self.endOffset > 0 && (child = self.endContainer.childNodes[self.endOffset - 1]) && child.nodeType == 1 && !K(child).isSingle()) { 307 self.setEnd(child, child.childNodes.length); 308 } 309 return self; 310 } 311 }); 312 function _getDoc(node) { 313 if (!node) { 314 return document; 315 } 316 return node.ownerDocument || node.document || node; 317 } 318 function _getWin(node) { 319 if (!node) { 320 return window; 321 } 322 var doc = _getDoc(node); 323 return doc.parentWindow || doc.defaultView; 324 } 325 function _getSel(doc) { 326 var win = _getWin(doc); 327 return _IERANGE ? doc.selection : win.getSelection(); 328 } 329 function _getRng(doc) { 330 var sel = _getSel(doc), rng; 331 try { 332 if (sel.rangeCount > 0) { 333 rng = sel.getRangeAt(0); 334 } else { 335 rng = sel.createRange(); 336 } 337 } catch(e) {} 338 if (_IERANGE && (!rng || (!rng.item && rng.parentElement().ownerDocument !== doc))) { 339 return null; 340 } 341 return rng; 342 } 343 function KCmd(range) { 344 this.init(range); 345 } 346 _extend(KCmd,{ 347 init : function(range) { 348 var self = this, doc = range.doc; 349 self.doc = doc; 350 self.win = _getWin(doc); 351 self.sel = _getSel(doc); 352 self.range = range; 353 }, 354 selection : function(forceReset) { 355 var self = this, doc = self.doc, rng = _getRng(doc); 356 self.sel = _getSel(doc); 357 if (rng) { 358 self.range = _range(rng); 359 return self; 360 } 361 return self; 362 }, 363 inserthtml : function(val, quickMode) { 364 var self = this, range = self.range; 365 if (val === ‘‘) { 366 return self; 367 } 368 function insertHtml(range, val) { 369 var doc = range.doc, 370 frag = doc.createDocumentFragment(); 371 function parseHTML(htmlStr, fragment) { 372 var div = document.createElement("div"), reSingleTag = /^<(\w+)\s*\/?>$/; 373 htmlStr += ‘‘; 374 if (reSingleTag.test(htmlStr)) { 375 return [ document.createElement(RegExp.$1) ]; 376 } 377 var tagWrap = { 378 option : [ "select" ], 379 optgroup : [ "select" ], 380 tbody : [ "table" ], 381 thead : [ "table" ], 382 tfoot : [ "table" ], 383 tr : [ "table", "tbody" ], 384 td : [ "table", "tbody", "tr" ], 385 th : [ "table", "thead", "tr" ], 386 legend : [ "fieldset" ], 387 caption : [ "table" ], 388 colgroup : [ "table" ], 389 col : [ "table", "colgroup" ], 390 li : [ "ul" ], 391 link : [ "div" ] 392 }; 393 for ( var param in tagWrap) { 394 var tw = tagWrap[param]; 395 switch (param) { 396 case "option": 397 tw.pre = ‘<select multiple="multiple">‘; 398 break; 399 case "link": 400 tw.pre = ‘fixbug<div>‘; 401 break; 402 default: 403 tw.pre = "<" + tw.join("><") + ">"; 404 } 405 tw.post = "</" + tw.reverse().join("></") + ">"; 406 } 407 var reMultiTag = /<\s*([\w\:]+)/, match = htmlStr.match(reMultiTag), tag = match ? match[1] 408 .toLowerCase() 409 : ""; 410 if (match && tagWrap[tag]) { 411 var wrap = tagWrap[tag]; 412 div.innerHTML = wrap.pre + htmlStr + wrap.post; 413 n = wrap.length; 414 while (--n >= 0) 415 div = div.lastChild; 416 } else { 417 div.innerHTML = htmlStr; 418 } 419 if (/^\s/.test(htmlStr)) 420 div.insertBefore(document 421 .createTextNode(htmlStr.match(/^\s*/)[0]), 422 div.firstChild); 423 if (fragment) { 424 var firstChild; 425 while ((firstChild = div.firstChild)) { 426 fragment.appendChild(firstChild); 427 } 428 return fragment; 429 } 430 return div.children; 431 }; 432 var oFrag = document.createDocumentFragment(); 433 frag.appendChild(parseHTML(val,oFrag)); 434 // range.deleteContents(); 435 range.insertNode(frag); 436 // range.collapse(false); 437 // self.select(false); 438 } 439 insertHtml(range, val); 440 return self; 441 } 442 }); 443 function _range(mixed) { 444 if (!mixed.nodeName) { 445 return mixed.constructor === KRange ? mixed : _toRange(mixed); 446 } 447 return new KRange(mixed); 448 } 449 function _cmd(mixed) { 450 if (mixed.nodeName) { 451 var doc = _getDoc(mixed); 452 mixed = _range(doc).selectNodeContents(doc.body).collapse(false); 453 } 454 return new KCmd(mixed); 455 }
使用的话,
var t=$(‘editor_iframe‘).contentDocument || $(‘editor_iframe‘).contentWindow.document,cmd=_cmd(doc); bind(t,‘mouseup‘,function(e){ cmd.selection(); }); bind($(‘editor_image‘),‘click‘,function(e){ cmd.inserthtml("<img src=‘http://localhost/my_editor/1.jpg‘>"); });
_cmd(doc).inserthtml("<img src=‘http://localhost/my_editor/1.jpg‘>");就行了。
doc=win.document,doc是iframe window的document.
代码其实很少。
最后说下,这种方案是基于iframe的,而贴吧,知乎的编辑器都是基于div contenteditable=true的。在contenteditable中是否行的通,我没有试过。
iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)
标签:
原文地址:http://www.cnblogs.com/TheViper/p/4217305.html