通过jQuery Bootstrap小插件,框任何一个div转换变成一个富文本编辑框,主要特色:
- 在Mac和window平台下自动针对常用操作绑定热键
- 可以拖拽插入图片,支持图片上传(也可以获取移动设备上的照片)
- 依赖于浏览器标准,没有标准代码;工具条和键盘均可定制,并且能够执行任何浏览器支持的命令
首先看一下效果:
接下来,上代码:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="utf-8"> 5 <title>Hello, Bootstrap</title> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 <meta name="keywords" content="opensource rich wysiwyg text editor jquery bootstrap execCommand html5" /> 8 <meta name="description" content="This tiny jQuery Bootstrap WYSIWYG plugin turns any DIV into a HTML5 rich text editor" /> 9 <link rel="shortcut icon" href="http://mindmup.s3.amazonaws.com/lib/img/favicon.ico" > 10 <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.no-icons.min.css" rel="stylesheet"> 11 <link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-responsive.min.css" rel="stylesheet"> 12 <link href="http://netdna.bootstrapcdn.com/font-awesome/3.0.2/css/font-awesome.css" rel="stylesheet"> 13 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script> 14 15 <script src="external/jquery.hotkeys.js"></script> 16 <script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script> 17 <script src="external/google-code-prettify/prettify.js"></script> 18 <link href="index.css" rel="stylesheet"> 19 <script src="bootstrap-my.js"></script> 20 <script src="indexexercise.js" ></script> 21 </head> 22 <body> 23 24 <div class="container"style="width:651px;"> 25 <div class="hero-unit" style="width: 600px; padding: 8px 30px 30px 30px;"> 26 <div id="alerts"></div> 27 <div class="btn-toolbar" data-role="editor-toolbar" data-target="#editor"> 28 <div class="btn-group"> 29 <a class="btn dropdown-toggle" data-toggle="dropdown" title="Font"><i class="icon-font"></i><b class="caret"></b></a> 30 <ul class="dropdown-menu"> 31 </ul> 32 </div> 33 <div class="btn-group"> 34 <a class="btn dropdown-toggle" data-toggle="dropdown" title="Font Size"><i class="icon-text-height"></i> <b class="caret"></b></a> 35 <ul class="dropdown-menu"> 36 <li><a data-edit="fontSize 5"><font size="5">Huge</font></a></li> 37 <li><a data-edit="fontSize 3"><font size="3">Normal</font></a></li> 38 <li><a data-edit="fontSize 1"><font size="1">Small</font></a></li> 39 </ul> 40 </div> 41 <div class="btn-group"> 42 <a class="btn" data-edit="insertunorderedlist" title="Bullet list"><i class="icon-list-ul"></i></a> 43 <a class="btn" data-edit="insertorderedlist" title="Number list"><i class="icon-list-ol"></i></a> 44 </div> 45 <div class="btn-group"> 46 <a class="btn" title="Insert picture (or just drag & drop)" id="pictureBtn"><i class="icon-picture"></i></a> 47 <input type="file" data-role="magic-overlay" data-target="#pictureBtn" data-edit="insertImage" /> 48 </div> 49 <input type="text" data-edit="inserttext" id="voiceBtn" x-webkit-speech=""> 50 </div> 51 52 <div id="editor" style="width: 568px;height: 145px;"> 53 welcome to edit in your space … 54 </div> 55 </div> 56 </div> 57 </html>
对应的indexexercise.js部分:
1 jQuery(document).ready(function(){ 2 3 $(function(){ 4 function initToolbarBootstrapBindings() { 5 var fonts = [‘Serif‘, ‘Sans‘, ‘Arial‘, ‘Arial Black‘, ‘Courier‘, 6 ‘Courier New‘, ‘Comic Sans MS‘, ‘Helvetica‘, ‘Impact‘, ‘Lucida Grande‘, ‘Lucida Sans‘, ‘Tahoma‘, ‘Times‘, 7 ‘Times New Roman‘, ‘Verdana‘], 8 fontTarget = $(‘[title=Font]‘).siblings(‘.dropdown-menu‘); 9 $.each(fonts, function (idx, fontName) { 10 fontTarget.append($(‘<li><a data-edit="fontName ‘ + fontName +‘" style="font-family:\‘‘+ fontName +‘\‘">‘+fontName + ‘</a></li>‘)); 11 }); 12 $(‘a[title]‘).tooltip({container:‘body‘}); 13 $(‘.dropdown-menu input‘).click(function() {return false;}) 14 .change(function () {$(this).parent(‘.dropdown-menu‘).siblings(‘.dropdown-toggle‘).dropdown(‘toggle‘);}) 15 .keydown(‘esc‘, function () {this.value=‘‘;$(this).change();}); 16 17 $(‘[data-role=magic-overlay]‘).each(function () { 18 var overlay = $(this), target = $(overlay.data(‘target‘)); 19 overlay.css(‘opacity‘, 0).css(‘position‘, ‘absolute‘).offset(target.offset()).width(target.outerWidth()).height(target.outerHeight()); 20 }); 21 if ("onwebkitspeechchange" in document.createElement("input")) { 22 var editorOffset = $(‘#editor‘).offset(); 23 $(‘#voiceBtn‘).css(‘position‘,‘absolute‘).offset({top: editorOffset.top, left: editorOffset.left+$(‘#editor‘).innerWidth()-35}); 24 } else { 25 $(‘#voiceBtn‘).hide(); 26 } 27 }; 28 function showErrorAlert (reason, detail) { 29 var msg=‘‘; 30 if (reason===‘unsupported-file-type‘) { msg = "Unsupported format " +detail; } 31 else { 32 console.log("error uploading file", reason, detail); 33 } 34 $(‘<div class="alert"> <button type="button" class="close" data-dismiss="alert">×</button>‘+ 35 ‘<strong>File upload error</strong> ‘+msg+‘ </div>‘).prependTo(‘#alerts‘); 36 }; 37 initToolbarBootstrapBindings(); 38 $(‘#editor‘).wysiwyg({ fileUploadError: showErrorAlert} ); 39 window.prettyPrint && prettyPrint(); 40 }); 41 42 (function(i,s,o,g,r,a,m) 43 {i[‘GoogleAnalyticsObject‘]=r;i[r]=i[r]||function(){ 44 (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 45 m=s.getElementsByTagName(o)[0];a.async=1; 46 a.src=g;m.parentNode.insertBefore(a,m)}) 47 (window,document,‘script‘,‘//www.google-analytics.com/analytics.js‘,‘ga‘); 48 ga(‘create‘, ‘UA-37452180-6‘, ‘github.io‘); 49 ga(‘send‘, ‘pageview‘); 50 51 (function(d, s, id) { 52 var js, fjs = d.getElementsByTagName(s)[0]; 53 if (d.getElementById(id)) 54 return; 55 js = d.createElement(s); 56 js.id = id; 57 js.src = "http://connect.facebook.net/en_GB/all.js#xfbml=1"; 58 fjs.parentNode.insertBefore(js, fjs); 59 }(document, ‘script‘, ‘facebook-jssdk‘)); 60 61 !function(d,s,id){ 62 var js,fjs=d.getElementsByTagName(s)[0]; 63 if(!d.getElementById(id)){ 64 js=d.createElement(s); 65 js.id=id; 66 js.src="http://platform.twitter.com/widgets.js"; 67 fjs.parentNode.insertBefore(js,fjs);}} 68 (document,"script","twitter-wjs"); 69 })
对应的bootstrap-my.js部分:
1 (function ($) { 2 ‘use strict‘; 3 var readFileIntoDataUrl = function (fileInfo) { 4 var loader = $.Deferred(), 5 fReader = new FileReader(); 6 fReader.onload = function (e) { 7 loader.resolve(e.target.result); 8 }; 9 fReader.onerror = loader.reject; 10 fReader.onprogress = loader.notify; 11 fReader.readAsDataURL(fileInfo); 12 return loader.promise(); 13 }; 14 $.fn.cleanHtml = function () { 15 var html = $(this).html(); 16 return html && html.replace(/(<br>|\s|<div><br><\/div>| )*$/, ‘‘); 17 }; 18 $.fn.wysiwyg = function (userOptions) { 19 var editor = this, 20 selectedRange, 21 options, 22 toolbarBtnSelector, 23 updateToolbar = function () { 24 if (options.activeToolbarClass) { 25 $(options.toolbarSelector).find(toolbarBtnSelector).each(function () { 26 var command = $(this).data(options.commandRole); 27 if (document.queryCommandState(command)) { 28 $(this).addClass(options.activeToolbarClass); 29 } else { 30 $(this).removeClass(options.activeToolbarClass); 31 } 32 }); 33 } 34 }, 35 execCommand = function (commandWithArgs, valueArg) { 36 var commandArr = commandWithArgs.split(‘ ‘), 37 command = commandArr.shift(), 38 args = commandArr.join(‘ ‘) + (valueArg || ‘‘); 39 document.execCommand(command, 0, args); 40 updateToolbar(); 41 }, 42 bindHotkeys = function (hotKeys) { 43 $.each(hotKeys, function (hotkey, command) { 44 editor.keydown(hotkey, function (e) { 45 if (editor.attr(‘contenteditable‘) && editor.is(‘:visible‘)) { 46 e.preventDefault(); 47 e.stopPropagation(); 48 execCommand(command); 49 } 50 }).keyup(hotkey, function (e) { 51 if (editor.attr(‘contenteditable‘) && editor.is(‘:visible‘)) { 52 e.preventDefault(); 53 e.stopPropagation(); 54 } 55 }); 56 }); 57 }, 58 getCurrentRange = function () { 59 var sel = window.getSelection(); 60 if (sel.getRangeAt && sel.rangeCount) { 61 return sel.getRangeAt(0); 62 } 63 }, 64 saveSelection = function () { 65 selectedRange = getCurrentRange(); 66 }, 67 restoreSelection = function () { 68 var selection = window.getSelection(); 69 if (selectedRange) { 70 try { 71 selection.removeAllRanges(); 72 } catch (ex) { 73 document.body.createTextRange().select(); 74 document.selection.empty(); 75 } 76 77 selection.addRange(selectedRange); 78 } 79 }, 80 insertFiles = function (files) { 81 editor.focus(); 82 $.each(files, function (idx, fileInfo) { 83 if (/^image\//.test(fileInfo.type)) { 84 $.when(readFileIntoDataUrl(fileInfo)).done(function (dataUrl) { 85 execCommand(‘insertimage‘, dataUrl); 86 }).fail(function (e) { 87 options.fileUploadError("file-reader", e); 88 }); 89 } else { 90 options.fileUploadError("unsupported-file-type", fileInfo.type); 91 } 92 }); 93 }, 94 markSelection = function (input, color) { 95 restoreSelection(); 96 if (document.queryCommandSupported(‘hiliteColor‘)) { 97 document.execCommand(‘hiliteColor‘, 0, color || ‘transparent‘); 98 } 99 saveSelection(); 100 input.data(options.selectionMarker, color); 101 }, 102 bindToolbar = function (toolbar, options) { 103 toolbar.find(toolbarBtnSelector).click(function () { 104 restoreSelection(); 105 editor.focus(); 106 execCommand($(this).data(options.commandRole)); 107 saveSelection(); 108 }); 109 toolbar.find(‘[data-toggle=dropdown]‘).click(restoreSelection); 110 111 toolbar.find(‘input[type=text][data-‘ + options.commandRole + ‘]‘).on(‘webkitspeechchange change‘, function () { 112 var newValue = this.value; /* ugly but prevents fake double-calls due to selection restoration */ 113 this.value = ‘‘; 114 restoreSelection(); 115 if (newValue) { 116 editor.focus(); 117 execCommand($(this).data(options.commandRole), newValue); 118 } 119 saveSelection(); 120 }).on(‘focus‘, function () { 121 var input = $(this); 122 if (!input.data(options.selectionMarker)) { 123 markSelection(input, options.selectionColor); 124 input.focus(); 125 } 126 }).on(‘blur‘, function () { 127 var input = $(this); 128 if (input.data(options.selectionMarker)) { 129 markSelection(input, false); 130 } 131 }); 132 toolbar.find(‘input[type=file][data-‘ + options.commandRole + ‘]‘).change(function () { 133 restoreSelection(); 134 if (this.type === ‘file‘ && this.files && this.files.length > 0) { 135 insertFiles(this.files); 136 } 137 saveSelection(); 138 this.value = ‘‘; 139 }); 140 }, 141 initFileDrops = function () { 142 editor.on(‘dragenter dragover‘, false) 143 .on(‘drop‘, function (e) { 144 var dataTransfer = e.originalEvent.dataTransfer; 145 e.stopPropagation(); 146 e.preventDefault(); 147 if (dataTransfer && dataTransfer.files && dataTransfer.files.length > 0) { 148 insertFiles(dataTransfer.files); 149 } 150 }); 151 }; 152 options = $.extend({}, $.fn.wysiwyg.defaults, userOptions); 153 toolbarBtnSelector = ‘a[data-‘ + options.commandRole + ‘],button[data-‘ + options.commandRole + ‘],input[type=button][data-‘ + options.commandRole + ‘]‘; 154 bindHotkeys(options.hotKeys); 155 if (options.dragAndDropImages) { 156 initFileDrops(); 157 } 158 bindToolbar($(options.toolbarSelector), options); 159 editor.attr(‘contenteditable‘, true) 160 .on(‘mouseup keyup mouseout‘, function () { 161 saveSelection(); 162 updateToolbar(); 163 }); 164 $(window).bind(‘touchend‘, function (e) { 165 var isInside = (editor.is(e.target) || editor.has(e.target).length > 0), 166 currentRange = getCurrentRange(), 167 clear = currentRange && (currentRange.startContainer === currentRange.endContainer && currentRange.startOffset === currentRange.endOffset); 168 if (!clear || isInside) { 169 saveSelection(); 170 updateToolbar(); 171 } 172 }); 173 return this; 174 }; 175 $.fn.wysiwyg.defaults = { 176 hotKeys: { 177 ‘ctrl+b meta+b‘: ‘bold‘, 178 ‘ctrl+i meta+i‘: ‘italic‘, 179 ‘ctrl+u meta+u‘: ‘underline‘, 180 ‘ctrl+z meta+z‘: ‘undo‘, 181 ‘ctrl+y meta+y meta+shift+z‘: ‘redo‘, 182 ‘ctrl+l meta+l‘: ‘justifyleft‘, 183 ‘ctrl+r meta+r‘: ‘justifyright‘, 184 ‘ctrl+e meta+e‘: ‘justifycenter‘, 185 ‘ctrl+j meta+j‘: ‘justifyfull‘, 186 ‘shift+tab‘: ‘outdent‘, 187 ‘tab‘: ‘indent‘ 188 }, 189 toolbarSelector: ‘[data-role=editor-toolbar]‘, 190 commandRole: ‘edit‘, 191 activeToolbarClass: ‘btn-info‘, 192 selectionMarker: ‘edit-focus-marker‘, 193 selectionColor: ‘darkgrey‘, 194 dragAndDropImages: true, 195 fileUploadError: function (reason, detail) { console.log("File upload error", reason, detail); } 196 }; 197 }(window.jQuery));
对应的样式设计:
1 #editor { 2 max-height: 250px; 3 height: 250px; 4 background-color: white; 5 border-collapse: separate; 6 border: 1px solid rgb(204, 204, 204); 7 padding: 4px; 8 box-sizing: content-box; 9 -webkit-box-shadow: rgba(0, 0, 0, 0.0745098) 0px 1px 1px 0px inset; 10 box-shadow: rgba(0, 0, 0, 0.0745098) 0px 1px 1px 0px inset; 11 border-top-right-radius: 3px; border-bottom-right-radius: 3px; 12 border-bottom-left-radius: 3px; border-top-left-radius: 3px; 13 overflow: scroll; 14 outline: none; 15 } 16 #voiceBtn { 17 width: 20px; 18 color: transparent; 19 background-color: transparent; 20 transform: scale(2.0, 2.0); 21 -webkit-transform: scale(2.0, 2.0); 22 -moz-transform: scale(2.0, 2.0); 23 border: transparent; 24 cursor: pointer; 25 box-shadow: none; 26 -webkit-box-shadow: none; 27 } 28 29 div[data-role="editor-toolbar"] { 30 -webkit-user-select: none; 31 -moz-user-select: none; 32 -ms-user-select: none; 33 user-select: none; 34 } 35 36 .dropdown-menu a { 37 cursor: pointer; 38 }
如有错误之处,敬请指正,谢谢
时间: 2024-10-12 08:59:14