MyException - 我的异常网
当前位置:我的异常网» JavaScript » iframe从光标处插入图片(失去焦点后依然可以在原位

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

www.MyException.Cn  网友分享于:2015-02-11  浏览:0次
iframe从光标处插入图片(失去焦点后仍然可以在原位置插入)

转载请注明: 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中是否行的通,我没有试过。

附件

文章评论

科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
我的丈夫是个程序员
我的丈夫是个程序员
中美印日四国程序员比较
中美印日四国程序员比较
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
每天工作4小时的程序员
每天工作4小时的程序员
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
漫画:程序员的工作
漫画:程序员的工作
编程语言是女人
编程语言是女人
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
总结2014中国互联网十大段子
总结2014中国互联网十大段子
一个程序员的时间管理
一个程序员的时间管理
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
代码女神横空出世
代码女神横空出世
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
旅行,写作,编程
旅行,写作,编程
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
我是如何打败拖延症的
我是如何打败拖延症的
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
为什么程序员都是夜猫子
为什么程序员都是夜猫子
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
程序员应该关注的一些事儿
程序员应该关注的一些事儿
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
如何成为一名黑客
如何成为一名黑客
那些争议最大的编程观点
那些争议最大的编程观点
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
程序员都该阅读的书
程序员都该阅读的书
鲜为人知的编程真相
鲜为人知的编程真相
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
老程序员的下场
老程序员的下场
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
程序员的鄙视链
程序员的鄙视链
程序员和编码员之间的区别
程序员和编码员之间的区别
Java程序员必看电影
Java程序员必看电影
程序员必看的十大电影
程序员必看的十大电影
10个调试和排错的小建议
10个调试和排错的小建议
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有