iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)

时间:2021-07-29 10:56:08

转载请注明: TheViper http://www.cnblogs.com/TheViper 

改进版:更简单的 编辑器从光标处插入图片(失去焦点后仍然可以在原位置插入)

为什么会有这个需求?

当我们需要一个文本输入框(编辑器),它的功能定位介于专门的富文本编辑器和裸(原生)文本框之间。这时,如果用专门富文本编辑器,如kindeditor,ueditor,显的很大材小用,而且这两个的体积都不小,而体积小的富文本编辑器又是针对现代浏览器的。

贴吧发帖和知乎发问题的编辑器就是典型的这种需求

iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)

iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)

问题的出现

下面是这个问题的呈现,ie8下,知乎编辑器中插入图片

iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)

首先将光标移到已经输入文字的任意位置,然后让光标在编辑器中失去焦点。上传图片,最后看到图片并没有在光标最后在编辑器中的位置。

如果没有失去焦点,或者浏览器是现代浏览器,则不存在这个问题。

网上的解决方案

网上有很多方案,但至少到现在,我没有看到一个成功的。一般问题都出在编辑器失去焦点后,不能“智能”的在光标原位置插入图片。

最接近的方案就是很简单的win.document.execCommand("insertImage", '',data);,但这个只能针对现代浏览器。

我的成功解决方案

先看下效果

firefox

iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)

ie8

iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)

注意到在kindeditor,ueditor中,都很好的解决了这个问题,那最简单的方法就是从源码中扒出那些代码。ueditor的源码复杂点,多点,就从kindeditor中扒。kindeditor的源码也就5000多行,还没jquery多.

kindeditor里面的思路是每次mouseup,keyup的时候,就重新锁定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的封装。

扒下来的代码

  1 define('editor', function() {
  2     function _extend(child, parent, proto) {
  3         if (!proto) {
  4             proto = parent;
  5             parent = null;
  6         }
  7         var childProto;
  8         if (parent) {
  9             var fn = function () {};
 10             fn.prototype = parent.prototype;
 11             childProto = new fn();
 12             _each(proto, function(key, val) {
 13                 childProto[key] = val;
 14             });
 15         } else {
 16             childProto = proto;
 17         }
 18         childProto.constructor = child;
 19         child.prototype = childProto;
 20         child.parent = parent ? parent.prototype : null;
 21     }
 22     function _iframeDoc(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         var startNode = doc, startPos = 0, cmp = -1;
 42         var testRange = rng.duplicate();
 43         _moveToElementText(testRange, parent);
 44         for (var i = 0, len = nodes.length; i < len; i++) {
 45             var node = nodes[i];
 46             cmp = testRange.compareEndPoints('StartToStart', pointRange);
 47             if (cmp === 0) {
 48                 return {node: node.parentNode, offset: i};
 49             }
 50             if (node.nodeType == 3) {
 51                 testRange.moveStart('character', node.nodeValue.length);
 52                 startPos += node.nodeValue.length;
 53             }
 54             if (cmp < 0) {
 55                 startNode = node;
 56             }
 57         }
 58         testRange = rng.duplicate();
 59         _moveToElementText(testRange, parent);
 60         testRange.setEndPoint('StartToEnd', pointRange);
 61         startPos -= testRange.text.replace(/\r\n|\n|\r/g, '').length;
 62         if (cmp > 0 && startNode.nodeType == 3) {
 63             var prevNode = startNode.previousSibling;
 64             while (prevNode && prevNode.nodeType == 3) {
 65                 startPos -= prevNode.nodeValue.length;
 66                 prevNode = prevNode.previousSibling;
 67             }
 68         }
 69         return {node: startNode, offset: startPos};
 70     }
 71     function _toRange(rng) {
 72         var doc, range;
 73         if (_IERANGE) {
 74             if (rng.item) {
 75                 doc = _getDoc(rng.item(0));
 76                 range = new KRange(doc);
 77                 range.selectNode(rng.item(0));
 78                 return range;
 79             }
 80             doc = rng.parentElement().ownerDocument;
 81             var start = _getStartEnd(rng, true),
 82             end = _getStartEnd(rng, false);
 83             range = new KRange(doc);
 84             range.setStart(start.node, start.offset);
 85             range.setEnd(end.node, end.offset);
 86             return range;
 87         }
 88         var startContainer = rng.startContainer;
 89         doc = startContainer.ownerDocument || startContainer;
 90         range = new KRange(doc);
 91         range.setStart(startContainer, rng.startOffset);
 92         range.setEnd(rng.endContainer, rng.endOffset);
 93         return range;
 94     }
 95     function KRange(doc) {
 96         this.init(doc);
 97     }
 98     _extend(KRange,{
 99         init : function(doc) {
100             var self = this;
101             self.startContainer = doc;
102             self.startOffset = 0;
103             self.endContainer = doc;
104             self.endOffset = 0;
105             self.collapsed = true;
106             self.doc = doc;
107         },
108         setStart : function(node, offset) {
109             var self = this, doc = self.doc;
110             self.startContainer = node;
111             self.startOffset = offset;
112             if (self.endContainer === doc) {
113                 self.endContainer = node;
114                 self.endOffset = offset;
115             }
116             return _updateCollapsed(this);
117         },
118         setEnd : function(node, offset) {
119             var self = this, doc = self.doc;
120             self.endContainer = node;
121             self.endOffset = offset;
122             if (self.startContainer === doc) {
123                 self.startContainer = node;
124                 self.startOffset = offset;
125             }
126             return _updateCollapsed(this);
127         },
128         setStartBefore : function(node) {
129             return this.setStart(node.parentNode || this.doc, 0);
130         },
131         setEndAfter : function(node) {
132             return this.setEnd(node.parentNode ||1);
133         },
134         selectNode : function(node) {
135             return this.setStartBefore(node).setEndAfter(node);
136         },
137         selectNodeContents : function(node) {
138             return this.setStart(node, 0).setEnd(node, 0);
139         },
140         collapse : function(toStart) {
141             if (toStart) {
142                 return this.setEnd(this.startContainer, this.startOffset);
143             }
144             return this.setStart(this.endContainer, this.endOffset);
145         },
146         cloneRange : function() {
147             return new KRange(this.doc).setStart(this.startContainer, this.startOffset).setEnd(this.endContainer, this.endOffset);
148         },
149         toString : function() {
150             var rng = this.get(), str = _IERANGE ? rng.text : rng.toString();
151             return str.replace(/\r\n|\n|\r/g, '');
152         },
153         insertNode : function(node) {
154             var self = this,
155             sc = self.startContainer, so = self.startOffset,
156             ec = self.endContainer, eo = self.endOffset,
157             firstChild, lastChild, c, nodeCount = 1;
158             if (sc.nodeType == 1) {
159                 c = sc.childNodes[so];
160                 if (c) {
161                     sc.insertBefore(node, c);
162                     if (sc === ec) {
163                         eo += nodeCount;
164                     }
165                 } else {
166                     sc.appendChild(node);
167                 }
168             } else if (sc.nodeType == 3) {
169                 if (so === 0) {
170                     sc.parentNode.insertBefore(node, sc);
171                     if (sc.parentNode === ec) {
172                         eo += nodeCount;
173                     }
174                 } else if (so >= sc.nodeValue.length) {
175                     if (sc.nextSibling) {
176                         sc.parentNode.insertBefore(node, sc.nextSibling);
177                     } else {
178                         sc.parentNode.appendChild(node);
179                     }
180                 } else {
181                     if (so > 0) {
182                         c = sc.splitText(so);
183                     } else {
184                         c = sc;
185                     }
186                     sc.parentNode.insertBefore(node, c);
187                     if (sc === ec) {
188                         ec = c;
189                         eo -= so;
190                     }
191                 }
192             }
193             if (firstChild) {
194                 self.setStartBefore(firstChild).setEndAfter(lastChild);
195             } else {
196                 self.selectNode(node);
197             }
198             return self.setEnd(ec, eo);
199         }
200     });
201     function _getDoc(node) {
202         if (!node) {
203             return document;
204         }
205         return node.ownerDocument || node.document || node;
206     }
207     function _getWin(node) {
208         if (!node) {
209             return window;
210         }
211         var doc = _getDoc(node);
212         return doc.parentWindow || doc.defaultView;
213     }
214     function _getSel(doc) {
215         var win = _getWin(doc);
216         return _IERANGE ? doc.selection : win.getSelection();
217     }
218     function _getRng(doc) {
219         var sel = _getSel(doc), rng;
220         try {
221             if (sel.rangeCount > 0) {
222                 rng = sel.getRangeAt(0);
223             } else {
224                 rng = sel.createRange();
225             }
226         } catch(e) {}
227         if (_IERANGE && (!rng || (!rng.item && rng.parentElement().ownerDocument !== doc))) {
228             return null;
229         }
230         return rng;
231     }
232     function KCmd(range) {
233         this.init(range);
234     }
235     _extend(KCmd,{
236         init : function(range) {
237             var self = this, doc = range.doc;
238             self.doc = doc;
239             self.win = _getWin(doc);
240             self.sel = _getSel(doc);
241             self.range = range;
242         },
243         selection : function(forceReset) {
244             var self = this, doc = self.doc, rng = _getRng(doc);
245             self.sel = _getSel(doc);
246             if (rng) {
247                 self.range = _range(rng);
248                 return self;
249             }
250             return self;
251         },
252         inserthtml : function(val, quickMode) {
253             var self = this, range = self.range;
254             if (val === '') {
255                 return self;
256             }
257             function insertHtml(range, val) {
258                 var doc = range.doc,
259                 frag = doc.createDocumentFragment();
260                 function parseHTML(htmlStr, fragment) {
261                     var div = document.createElement("div"), reSingleTag = /^<(\w+)\s*\/?>$/;
262                     htmlStr += '';
263                     if (reSingleTag.test(htmlStr)) {
264                         return [ document.createElement(RegExp.$1) ];
265                     }
266                     var tagWrap = {
267                         option : [ "select" ],
268                         optgroup : [ "select" ],
269                         tbody : [ "table" ],
270                         thead : [ "table" ],
271                         tfoot : [ "table" ],
272                         tr : [ "table", "tbody" ],
273                         td : [ "table", "tbody", "tr" ],
274                         th : [ "table", "thead", "tr" ],
275                         legend : [ "fieldset" ],
276                         caption : [ "table" ],
277                         colgroup : [ "table" ],
278                         col : [ "table", "colgroup" ],
279                         li : [ "ul" ],
280                         link : [ "div" ]
281                     };
282                     for ( var param in tagWrap) {
283                         var tw = tagWrap[param];
284                         switch (param) {
285                             case "option":
286                             tw.pre = '<select multiple="multiple">';
287                             break;
288                             case "link":
289                             tw.pre = 'fixbug<div>';
290                             break;
291                             default:
292                             tw.pre = "<" + tw.join("><") + ">";
293                         }
294                         tw.post = "</" + tw.reverse().join("></") + ">";
295                     }
296                     var reMultiTag = /<\s*([\w\:]+)/, match = htmlStr.match(reMultiTag), tag = match ? match[1]
297                     .toLowerCase()
298                     : "";
299                     if (match && tagWrap[tag]) {
300                         var wrap = tagWrap[tag];
301                         div.innerHTML = wrap.pre + htmlStr + wrap.post;
302                         n = wrap.length;
303                         while (--n >= 0)
304                             div = div.lastChild;
305                     } else {
306                         div.innerHTML = htmlStr;
307                     }
308                     if (/^\s/.test(htmlStr))
309                         div.insertBefore(document
310                             .createTextNode(htmlStr.match(/^\s*/)[0]),
311                             div.firstChild);
312                     if (fragment) {
313                         var firstChild;
314                         while ((firstChild = div.firstChild)) {
315                             fragment.appendChild(firstChild);
316                         }
317                         return fragment;
318                     }
319                     return div.children;
320                 };
321                 var oFrag = document.createDocumentFragment();
322                 frag.appendChild(parseHTML(val,oFrag));
323                 range.insertNode(frag);
324             }
325             insertHtml(range, val);
326             return self;
327         }
328     });
329     function _range(mixed) {
330         if (!mixed.nodeName) {
331             return mixed.constructor === KRange ? mixed : _toRange(mixed);
332         }
333         return new KRange(mixed);
334     }
335     function _cmd(mixed) {
336         if (mixed.nodeName) {
337             var doc = _getDoc(mixed);
338             mixed = _range(doc).selectNodeContents(doc.body).collapse(false);
339         }
340         return new KCmd(mixed);
341     }
342     return _cmd;
343 });

使用的话,

var t=$('editor_iframe').contentDocument || $('editor_iframe').contentWindow.document,cmd=_cmd(doc);
bind(t,'mouseup keyup',function(e){
     if(e.keyCode==13)
$('editor_iframe').focus();//在ie中如果回车键换行,编辑器会失去焦点,不会在原位置插入 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的,这个用上面的方法也行的通。

附件