网上有些免费的在线HTML编辑器,有的功能实在是过于强大但显得臃肿不堪;有的外表轻松紧凑但又没有直接的源码提供。根据163、sina、sohu等门户网站邮箱里的编辑器样式,个人改造了一个功能简单的Editor:
编辑器一般包括二个部分: 工具条和文本区,文本区又可以通过按钮进行源码方式和文本方式的切换即存在文本域与源码域。
首先是采用一个标准的样式表来规范编辑器的页面,有以下基于图形toolbar.gif的文件editor.css:
toolbar.gif如下:
editor.css如下:
#dvPanel {...}{ position:relative;width:600px;height:240px; }
#dvToobar{...}{ padding-left:3px;padding-top:2px;background-color: #EEEEEE; border-top:1px solid #AAAAAA;border-right:1px solid #AAAAAA;border-left:1px solid #AAAAAA; height:26px; }
#divEditor{...}{background-color:#EEEEEE;border-top:1px solid #AAAAAA;border-right:1px solid #AAAAAA; border-left:1px solid #AAAAAA;border-bottom:1px solid #AAAAAA;height:224px;}
#dvToobar a {...}{background-repeat:no-repeat;display:block;float:left;margin-right:3px;border:1px solid #dfdfdf;border-top:1px solid #E2E3E2;border-bottom:1px solid #E2E3E2; background-image:url(toolbar.gif);height:20px; font-size:0px;}
#dvToobar a:hover{...}{border-top:1px solid #CCC;border-right:1px solid #999;border-bottom:1px solid #999;border-left:1px solid #CCC;background-color:#FFF}
#dvToobar a.icoCut{...}{background-position:-1px -3px;width:22px; }
#dvToobar a.icoCpy{...}{background-position:-28px -3px; width:24px; }
#dvToobar a.icoPse{...}{background-position:-57px -3px; width:52px}
#dvToobar a.icoWgt{...}{background-position:-108px -3px; width:24px}
#dvToobar a.icoIta{...}{background-position:-136px -3px; width:22px}
#dvToobar a.icoUln{...}{background-position:-164px -3px; width:24px}
#dvToobar a.icoAgn{...}{background-position:-192px -3px; width:24px;}
#dvToobar a.icoLst{...}{background-position:-220px -3px; width:24px;}
#dvToobar a.icoOdt{...}{background-position:-248px -3px; width:26px;}
#dvToobar a.icoFcl{...}{background-position:-332px -1px; width:24px;}
#dvToobar a.icoUrl{...}{background-position:-360px -3px; width:24px;}
#dvToobar a.icoImg{...}{background-position:-388px -1px; width:24px;}
#dvToobar a.icoMfc{...}{background-position:-416px -3px; width:24px;}
#dvToobar a.icoHtm{...}{background-position:-442px -3px; width:44px;}
#dvAlign {...}{ background:#FFFFFF;border:1px solid #838383;padding:1px; width:60px;height:60px;}
#dvAlign a{...}{font-size:12px; height:16px;line-height:16px; display:block;padding:2px;color:#000000;text-decoration:none;position:relative}
#dvAlign a:hover{...}{ background:#E5E5E5}
#dvOrder {...}{ background:#FFFFFF;border:1px solid #838383;padding:1px; width:60px;height:40px;}
#dvOrder a{...}{font-size:12px; height:16px;line-height:16px; display:block;padding:2px;color:#000000;text-decoration:none;position:relative}
#dvOrder a:hover{...}{ background:#E5E5E5}
#dvDent {...}{ background:#FFFFFF;border:1px solid #838383;padding:1px; width:60px;height:40px;}
#dvDent a{...}{font-size:12px; height:16px;line-height:16px; display:block;padding:2px;color:#000000;text-decoration:none;position:relative}
#dvDent a:hover{...}{ background:#E5E5E5}
#dvForeColor {...}{ position:relative;left:50px;background:#FFFFFF;border:1px solid #838383;width:90px;}
#dvForeColor a{...}{font-size:12px;height:16px;line-height:16px;display:block;padding:2px;color:#000000;text-decoration:none;position:relative}
#dvForeColor a:hover{...}{ background:#E5E5E5}
#dvForeColor span{...}{position:absolute;left:19px;top:0px;cursor:hand!important;cursor:pointer;color:#333}
#dvForeColor b{...}{font-size:0;display:block;width:10px;height:8px;position:relative;left:4px;top:1px;cursor:hand!important;cursor:pointer}
.fontfamily {...}{ position: relative;width:100px;height:22px;font-size:12px;float:left;margin-right:5px;}
.fontsize {...}{ position: relative;width: 50px;height:22px;font-size:12px;float:left;margin-right:5px;}
.HtmlEditor {...}{ width:100%; height:100%; }
.sourceEditor{...}{
height:100%;
width:100%;
border:0px;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
white-space: -pre-wrap;
white-space: -o-pre-wrap;
word-wrap: break-word;
}
在初始化函数中我们必须先加载这一样式单:
loadcss:function () ...{
var links= document.createElement("Link");
links.setAttribute("type","text/css");
links.setAttribute("rel","stylesheet");
links.setAttribute("href","editor.css");
document.getElementsByTagName("head")[0].appendChild(links);
}
这样就可以准备装载工具条了:
工具条包括了字体字号、粘贴复制、粗体倾斜下划链接等,其中对齐编号缩进颜色包括多项这里我也采用了浮层菜单的方式。对工具条的加载包括三个部分:
1.对象或者对象元素数组的的初始化(代码部分就不解释了)
var EditorKit=...{
mode: 1, //change the appearence between HTML and SOURCE
FontColors:...{ "0":["暗红色","#800000"],"1":["紫色","#800080"],"2":["红色","#F00000"],"3":["鲜粉色","#F000F0"],"4":["深蓝色","#000080"],"5":["蓝色","#0000F0"],"6":["湖蓝色","#00F0F0"],"7":["蓝绿色","#008080"],"8":["绿色","#008000"],"9":["橄榄色","#808000"],"10":["浅绿色","#00F000"],"11":["橙黄色","#F0C000"],"12":["黑色","#000000"],"13":["灰色","#808080"],"14":["银色","#C0C0C0"],"15":["白色","#FFFFFF"] },
fonts:["字体","宋体","黑体","楷体_GB2312","隶书","幼圆","Arial","Arial Narrow","Arial Black","Comic Sans MS","Courier","System","Times New Roman"],
sizes:["字号","1","2","3","4","5","6","7"],
aligns:...{"0":["Justifyleft","左对齐"],"1":["Justifycenter","居中对齐"],"2":["Justifyright","右对齐"]},
orders:...{"0":["Insertorderedlist","数字列表"],"1":["Insertunorderedlist","符号列表"]},
dents :...{"0":["Indent","增加缩进"],"1":["Outdent","减少缩进"]},
divs:["dvAlign","dvOrder","dvDent","dvForeColor","dvImgFace"],
//including the following methods;
}
2.工具对应的功能函数
format:function(type, para)...{
var f = window.frames["HtmlEditor"];
f.focus();
if(!para) f.document.execCommand(type);
else f.document.execCommand(type,false,para);
f.focus();
},
changeMode:function ()...{ var sourceEditor =document.getElementById("sourceEditor");
var HtmlEditor =document.getElementById("HtmlEditor");
var divEditor = document.getElementById("divEditor");
var f = window.frames["HtmlEditor"];
var body = f.document.getElementsByTagName("BODY")[0];
if(this.mode)...{
sourceEditor.style.display = "";
HtmlEditor.style.display = "none";
sourceEditor.value = body.innerHTML;
}else...{
sourceEditor.style.display = "none";
HtmlEditor.style.display = "";
body.innerHTML = sourceEditor.value;
}
this.mode=1-this.mode;
},
//隐藏所有已打开的菜单浮动层
hideAllMenu:function () ...{
for (each in this.divs)
...{
if(document.getElementById(this.divs[each])) document.getElementById(this.divs[each]).style.display="none";
}
},
//加载对齐下拉浮动菜单层;首次点击时进行
loadAlign: function () ...{
var aligndiv=document.createElement("div");
aligndiv.style.display="none";
aligndiv.setAttribute("id","dvAlign");
aligndiv.style.position="absolute";
//计算偏移位置,在单击按钮align正下方24px处;
var e=document.getElementById("alignbtn");
var x=e.offsetLeft;
var y=e.offsetTop;
while(e=e.offsetParent)...{ x+=e.offsetLeft; y+=e.offsetTop; }
y+=24;
aligndiv.style.left= x+"px";
aligndiv.style.top = y+"px";
var aligna;
for(each in this.aligns) ...{
aligna=document.createElement("A");
aligna.setAttribute("href","javascript:EditorKit.setAlign('"+this.aligns[each][0]+"')");
aligna.appendChild(document.createTextNode(this.aligns[each][1]));
aligndiv.appendChild(aligna);
}
document.getElementsByTagName("body")[0].appendChild(aligndiv);
},
showAlign:function () ...{
if(!document.getElementById("dvAlign")) this.loadAlign();
var aligndiv=document.getElementById("dvAlign");
this.hideAllMenu();
aligndiv.style.display="";
},
setAlign:function(type) ...{
this.format(type);
document.getElementById("dvAlign").style.display="none";
},
loadOrder:function() ...{
var orderdiv=document.createElement("div");
orderdiv.style.display="none";
orderdiv.setAttribute("id","dvOrder");
orderdiv.style.position="absolute";
//计算偏移位置,在单击按钮align正下方24px处;
var e=document.getElementById("orderbtn");
var x=e.offsetLeft;
var y=e.offsetTop;
while(e=e.offsetParent)...{ x+=e.offsetLeft; y+=e.offsetTop; }
y+=24;
orderdiv.style.left= x+"px";
orderdiv.style.top = y+"px";
var ordera;
for(each in this.orders) ...{
ordera=document.createElement("A");
ordera.setAttribute("href","javascript:EditorKit.setOrder('"+this.orders[each][0]+"')");
ordera.appendChild(document.createTextNode(this.orders[each][1]));
orderdiv.appendChild(ordera);
}
document.getElementsByTagName("body")[0].appendChild(orderdiv);
},
showOrder:function() ...{
if(!document.getElementById("dvOrder")) this.loadOrder();
var orderdiv=document.getElementById("dvOrder");
this.hideAllMenu();
orderdiv.style.display="";
},
setOrder:function(type) ...{
this.format(type);
document.getElementById("dvOrder").style.display="none";
},
loadDent:function() ...{
var dentdiv=document.createElement("div");
dentdiv.style.display="none";
dentdiv.setAttribute("id","dvDent");
dentdiv.style.position="absolute";
//计算偏移位置,在单击按钮align正下方24px处;
var e=document.getElementById("dentbtn");
var x=e.offsetLeft;
var y=e.offsetTop;
while(e=e.offsetParent)...{ x+=e.offsetLeft; y+=e.offsetTop; }
y+=24;
dentdiv.style.left= x+"px";
dentdiv.style.top = y+"px";
var denta;
for(each in this.dents) ...{
denta=document.createElement("A");
denta.setAttribute("href","javascript:EditorKit.setDent('"+this.dents[each][0]+"')");
denta.appendChild(document.createTextNode(this.dents[each][1]));
dentdiv.appendChild(denta);
}
document.getElementsByTagName("body")[0].appendChild(dentdiv);
},
showDent:function() ...{
if(!document.getElementById("dvDent")) this.loadDent();
var dentdiv=document.getElementById("dvDent");
this.hideAllMenu();
dentdiv.style.display="";
},
setDent:function(type) ...{
this.format(type);
document.getElementById("dvDent").style.display="none";
},
loadForeColor:function() ...{
var colordiv=document.createElement("div");
colordiv.setAttribute("id","dvForeColor");
colordiv.style.display="none";
colordiv.style.position="absolute";
var e=document.getElementById("foreColorbtn");
var x=e.offsetLeft;
var y=e.offsetTop;
while(e=e.offsetParent)...{ x+=e.offsetLeft; y+=e.offsetTop; }
y+=24;
colordiv.style.left= x+"px";
colordiv.style.top = y+"px";
var newcolor;
var block;
var stext;
for(each in this.FontColors) ...{
newcolor=document.createElement("A");
newcolor.setAttribute("href","javascript:EditorKit.setForeColor('"+this.FontColors[each][1]+"')");
block=document.createElement("B");
block.style.backgroundColor =this.FontColors[each][1];
stext=document.createElement("span");
stext.appendChild(document.createTextNode(this.FontColors[each][0]));
newcolor.appendChild(stext);
newcolor.insertBefore(block,stext);
colordiv.appendChild(newcolor);
}
document.getElementsByTagName("body")[0].appendChild(colordiv);
},
showForeColor:function() ...{
if(!document.getElementById("dvForeColor")) this.loadForeColor();
var foreColordiv=document.getElementById("dvForeColor");
this.hideAllMenu();
foreColordiv.style.display="";
},
setForeColor:function(para) ...{
this.format("forecolor",para);
document.getElementById("dvForeColor").style.display="none";
},
setFontname:function () ...{
var index=document.getElementById("fontfamily").selectedIndex;
if(index!=0) ...{
EditorKit.format("fontname",EditorKit.fonts[index]);
}
},
setFontsize:function () ...{
var index=document.getElementById("fontsize").selectedIndex;
if(index!=0) ...{
EditorKit.format("fontsize",EditorKit.sizes[index]);
}
},
showToolbar:function () ...{
document.getElementById("dvToobar").style.display="";
},
createLink: function () ...{
var sURL=window.prompt("请输入链接 (如:http://www.163.com/):", "http://");
if ((sURL!=null) && (sURL!="http://")) ...{ this.format("CreateLink", sURL); }
},
insertimgInit:function() ...{
var file=document.createElement("input");
file.style.display="none";
file.setAttribute("type","file");
file.setAttribute("id","imgname");
document.getElementsByTagName("body")[0].appendChild(file);
},
insertImg:function ()...{
if(!document.getElementById("imgname")) this.insertimgInit();
document.getElementById("imgname").click();
document.getElementById("imgname").onChange=document.getElementById("imgname").value!=""?this.format("InsertImage",document.getElementById("imgname").value):null;
}
在加载浮层菜单时候用到了元素绝对地址的计算,方法也很简单。
3.在初始化函数中添加这些元素
//加载工具条
var dvToobar=document.createElement("div");
dvToobar.setAttribute("id","dvToobar");
dvToobar.style.display="none";
//1.加载字体类型
var family=document.createElement("select");
family.className="fontfamily";
family.setAttribute("id","fontfamily");
family.attachEvent("onchange",EditorKit.setFontname);
var newfont;
for(each in this.fonts) ...{
newfont=document.createElement("option");
newfont.value=this.fonts[each];
newfont.appendChild(document.createTextNode(this.fonts[each]));
family.appendChild(newfont);
}
dvToobar.appendChild(family);
//2.加载字体大小
var fontsizes=document.createElement("select");
fontsizes.className="fontsize";
fontsizes.setAttribute("id","fontsize");
fontsizes.attachEvent("onchange",EditorKit.setFontsize);
var newsize;
for(each in this.sizes) ...{
newsize=document.createElement("option");
newsize.appendChild(document.createTextNode(this.sizes[each]));
fontsizes.appendChild(newsize);
}
dvToobar.appendChild(fontsizes);
//3.加载一些小按钮
//剪切
var cutBtn=document.createElement("A");
cutBtn.className="icoCut";
cutBtn.setAttribute("href","javascript:EditorKit.format('Cut')");
cutBtn.setAttribute("title","剪切");
dvToobar.appendChild(cutBtn);
//复制
var cpyBtn=document.createElement("A");
cpyBtn.className="icoCpy";
cpyBtn.setAttribute("href","javascript:EditorKit.format('Copy')");
cpyBtn.setAttribute("title","复制");
dvToobar.appendChild(cpyBtn);
//粘贴
var pseBtn=document.createElement("A");
pseBtn.className="icoPse";
pseBtn.setAttribute("href","javascript:EditorKit.format('Paste')");
pseBtn.setAttribute("title","粘贴");
dvToobar.appendChild(pseBtn);
//加粗
var bldBtn=document.createElement("A");
bldBtn.className="icoWgt";
bldBtn.setAttribute("href","javascript:EditorKit.format('Bold')");
bldBtn.setAttribute("title","加粗");
dvToobar.appendChild(bldBtn);
//斜体
var itaBtn=document.createElement("A");
itaBtn.className="icoIta";
itaBtn.setAttribute("href","javascript:EditorKit.format('Italic')");
itaBtn.setAttribute("title","斜体");
dvToobar.appendChild(itaBtn);
//下划线
var ulnBtn=document.createElement("A");
ulnBtn.className="icoUln";
ulnBtn.setAttribute("href","javascript:EditorKit.format('Underline')");
ulnBtn.setAttribute("title","下划线");
dvToobar.appendChild(ulnBtn);
//对齐
var agnBtn=document.createElement("A");
agnBtn.className="icoAgn";
agnBtn.setAttribute("href","javascript:EditorKit.showAlign()");
agnBtn.setAttribute("id","alignbtn");
agnBtn.setAttribute("title","对齐");
dvToobar.appendChild(agnBtn);
//编号
var lstBtn=document.createElement("A");
lstBtn.className="icoLst";
lstBtn.setAttribute("href","javascript:EditorKit.showOrder()");
lstBtn.setAttribute("id","orderbtn");
lstBtn.setAttribute("title","编号");
dvToobar.appendChild(lstBtn);
//缩进
var odtBtn=document.createElement("A");
odtBtn.className="icoOdt";
odtBtn.setAttribute("href","javascript:EditorKit.showDent()");
odtBtn.setAttribute("id","dentbtn");
odtBtn.setAttribute("title","缩进");
dvToobar.appendChild(odtBtn);
//字体颜色
var fclBtn=document.createElement("A");
fclBtn.className="icoFcl";
fclBtn.setAttribute("href","javascript:EditorKit.showForeColor()");
fclBtn.setAttribute("id","foreColorbtn");
fclBtn.setAttribute("title","字体颜色");
dvToobar.appendChild(fclBtn);
//超链接
var urlBtn=document.createElement("A");
urlBtn.className="icoUrl";
urlBtn.setAttribute("href","javascript:EditorKit.createLink()");
urlBtn.setAttribute("title","超链接");
dvToobar.appendChild(urlBtn);
//增加图片
var imgBtn=document.createElement("A");
imgBtn.className="icoImg";
imgBtn.setAttribute("href","javascript:EditorKit.insertImg()");
imgBtn.setAttribute("title","增加图片");
dvToobar.appendChild(imgBtn);
//魔法表情
var mfcBtn=document.createElement("A");
mfcBtn.className="icoMfc";
mfcBtn.setAttribute("href","javascript:loadimgface()");
mfcBtn.setAttribute("title","魔法表情");
dvToobar.appendChild(mfcBtn);
//模式切换
var htmBtn=document.createElement("A");
htmBtn.className="icoHtm";
htmBtn.setAttribute("href","javascript:EditorKit.changeMode()");
htmBtn.setAttribute("title","模式切换");
dvToobar.appendChild(htmBtn);
根据前面的分析,文本区的加载自然包括二个部分:
//文本面板: 包括一个框架和一个文本域
var txtpanel=document.createElement("div");
txtpanel.setAttribute("id","divEditor");
//一个框架,可以进行正常的编辑面板
var HtmlEditor=document.createElement("iframe");
HtmlEditor.frameBorder =0;
HtmlEditor.marginHeight=0;
HtmlEditor.marginWidth =0;
HtmlEditor.src="about:blank";
HtmlEditor.setAttribute("id","HtmlEditor");
HtmlEditor.className="HtmlEditor";
HtmlEditor.attachEvent("onfocus",EditorKit.showToolbar);
txtpanel.appendChild(HtmlEditor);
var sourceEditor=document.createElement("textarea");
sourceEditor.className="sourceEditor";
sourceEditor.setAttribute("id","sourceEditor");
txtpanel.appendChild(sourceEditor);
var dvpanel=document.createElement("div");
dvpanel.setAttribute("id","dvPanel");
dvpanel.appendChild(dvToobar);
dvpanel.appendChild(txtpanel);
document.getElementsByTagName("body")[0].appendChild(dvpanel);
这里我已经绑定了点击后就弹出隐藏的工具栏函数,参见前面的代码。
最后是激活这个编辑器控件:
aliveEditor:function () ...{
var f = window.frames["HtmlEditor"];
var html = '<HEAD></HEAD><BODY><div></div></BODY>';
f.document.open("text/html","replace");
f.document.write(html);
f.document.close();
f.document.designMode="on";
var sourceEditor =document.getElementById("sourceEditor");
sourceEditor.style.display = "none";
}
其实更好的方法是仅仅提供一个工具条的接口给页面使用,在实际中应努力做好需要时再加载且之加载一次,
而不再去关联实际需要的文本区,进而免去对文本区不必要的束缚。