图形编辑器:对齐功能的实现

Posted 前端西瓜哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图形编辑器:对齐功能的实现相关的知识,希望对你有一定的参考价值。

大家好,我是前端西瓜哥。这次来简单说说实现图形编辑器对齐功能的思路。

对齐功能

实现图形编辑器的对齐功能。

编辑器 github 地址:

https://github.com/F-star/suika

线上体验:

https://blog.fstars.wang/app/suika/

原理不复杂。

首先要指定多个图形,不能只有一个。因为一个的话是没有参照物的,只能自己参照自己,位移距离为 0。

先求出每个图形的 AABB 包围盒。

AABB(axis-aligned bounding box)指的是 轴对齐包围盒。指的是包围图形的矩形,但 4 条边平行于坐标轴。

如下图,最外层的就是 AABB 包围盒。

里面带有旋转角度的是 OBB 包围盒子,oriented bounding box (OBB)。优点是能更紧密的包裹图形,但带了旋转,在判断碰撞时要额外进行处理。

AABB 我用下面结构表示:

interface IBox2 
  minX: number;
  minY: number;
  maxX: number;
  maxY: number;

计算好全部 AABB 包围盒后,再计算这些包围盒共同组成的大包围盒,我暂且称其为 mixedBBox。这个大包围盒会作为对齐的参照

左对齐

首先是 左对齐

其实就是让所有图形的 AABB 包围盒的左边和 mixedBBox 的左侧对齐。

for (let i = 0; i < elements.length; i++) 
  const el = elements[i];
  const dx = mixedBBox.minX - bBoxes[i].minX;
  el.x += dx;

水平对齐(左右对齐)

水平对齐,指的是将多个图形的 x 坐标往中间靠拢。

让所有图形的包围盒的中心的垂直中线和 mixedBBox 的中心垂直中线对齐。

const centerX = mixedBBox.minX / 2 + mixedBBox.maxX / 2;
for (let i = 0; i < elements.length; i++) 
  const el = elements[i];
  const dx = centerX - (bBoxes[i].minX / 2 + bBoxes[i].maxX / 2);
  el.x += dx;

右对齐

所有图形的包围盒和 mixedBBox 右侧对齐:

for (let i = 0; i < elements.length; i++) 
  const el = elements[i];
  const dx = mixedBBox.maxX - bBoxes[i].maxX;
  el.x += dx;

顶对齐

for (let i = 0; i < elements.length; i++) 
  const el = elements[i];
  const dy = mixedBBox.minY - bBoxes[i].minY;
  el.y += dy;

垂直对齐(上下对齐)

for (let i = 0; i < elements.length; i++) 
  const el = elements[i];
  dy = centerY - (bBoxes[i].minY / 2 + bBoxes[i].maxY / 2);
  el.y += dy;

底对齐

for (let i = 0; i < elements.length; i++) 
  const el = elements[i];
 	const dy = mixedBBox.maxY - bBoxes[i].maxY;
  el.y += dy;

结尾

另外,如果图形编辑器还有一个舞台,也可以选中单个图形,让这个图形让舞台的盒作为 mixedBBox 进行对齐。

开发一款图形工具,会遇到很多有趣的简单几何算法小知识,解决后挺有成就感的。

我是前端西瓜哥,欢迎关注我,学习更多前端图形知识。

有几个关于javascript的功能实现的问题?

我做了个网站,现在要做一个文本编辑器(js)。但有些属性我不知道怎么获得:

1.如何获得文本域中光标的位置。(用来在光标位置加入图片)
2.如何获得被选中的文本。(用来加粗文本)
3.如何获得光标所以的行。(用来左对齐和右对齐)

备注:或者谁能给我点关于制作在线文本编辑器的资料。

<head>
<script>
var edit; //当前选择的文本编辑区域对象
var RangeType; //对象类别

function start()
Editor.document.designMode="ON";
Editor.document.open();
Editor.document.write(ikj.value);
Editor.document.close();

fnInit()

function setFocus()
Editor.focus();


function selectRange()
edit = Editor.document.selection.createRange();
RangeType = Editor.document.selection.type;


function execCommand(command,para)

setFocus();
selectRange();
if (para=="")
edit.execCommand(command)
else
edit.execCommand(command, false, arguments[1]);
Editor.focus();
if (RangeType != "Control") edit.select();



function doSelectC(str,el)

var Index = el.selectedIndex;
if (Index != 0)
el.selectedIndex = 0;

execCommand(str,el.options[Index].text);


function doSelectCl(str,el)

var Index = el.selectedIndex;
if (Index != 0)
el.selectedIndex = 0;

execCommand(str,"<"+el.options[Index].value+">");




function fnInit()
for (i=0; i<document.all.length; i++)
document.all(i).unselectable = "off";
getSystemFonts();


function getSystemFonts()

var a=dlgHelper.fonts.count;
var fArray = new Array();
var oOption = document.createElement("OPTION");
oOption.text = "字体";
oOption.value = "0";
selectFontName.add(oOption);
for (i = 1;i < dlgHelper.fonts.count;i++)

fArray[i] = dlgHelper.fonts(i);
var oOption = document.createElement("OPTION");

oOption.text = fArray[i];
oOption.Value = i;
selectFontName.add(oOption);



function formatfor(va)
var t=va.replace(/\\r/g,\'\');
t = t.replace(/(<(script|textarea|xmp|pre|style).*?>)([^\\r]*?)(<\\/\\2>)/img,
function ()return arguments[1]+arguments[3].replace(/\\n/g, "\\r")+arguments[4])
t = t.replace(/\\n/g, "");
return t


function fontsize(el)
var Index=el.selectedIndex
var addpre="<font size="+el.options[Index].value+">"
if(Index>7)addpre="<font style=\'font-size:"+el.options[Index].value+"pt\'>"

var oSel = Editor.document.selection.createRange()
var sBookmark = oSel.getBookmark()
var oSelhtml=oSel.htmlText
if(oSelhtml!="")

//定位选中内容

var conts=oSelhtml
var textLength = Editor.document.body.innerText.length
oSel.moveStart("character", -1*textLength)
var contp=formatfor(oSel.htmlText)
var conta=formatfor(Editor.document.body.innerHTML)

var contpa=\'\'
var partC=""
var partB=""
var partA=""

var m=0
m=conta.indexOf(contp.substr(0,3))

var f=contp.length
for(;f>0;f--)
if(conta.substr(m,f)==contp.substr(0, f))contpa=contp.substr(0,f);partC=conta.substr(m+f);break

var ko=contp.substr(f)
var kol=ko.length
var ty=conta.substr(m+f,kol)
var hu=""
for(var b=1;b<kol;b++)if(ko.substr(b)==ty.substr(0,kol-b))hu=ko.substr(b);contpa+=hu;partC=partC.substr(kol-b);break

var k=contpa.length
cont=conts.replace(/\\n/g, "")
var u=cont.length
if(cont==contpa.substr(k-u))partB=cont;partA=contpa.substr(0,k-u)else

for(u=cont.length;u>0;u--)
if(cont.lastIndexOf(contpa.substr(k-u))!=-1)partB0=contpa.substr(k-u);partA0=contpa.substr(0,k-u);break

contt=formatfor(conts)
if(hu!="")if(contt.substr(contt.length-kol)==ko)contt=contt.substr(0,contt.length-kol)+hu
u=contt.length
var youm=contpa.lastIndexOf(contt)
if(youm!=-1)partB=contt;partA=contpa.substr(0,youm);partC=contpa.substr(youm+u)+partCelse
for(;u>0;u--)if(contt.lastIndexOf(contpa.substr(k-u))!=-1)partB1=contpa.substr(k-u);partA1=contpa.substr(0,k-u);break

if(partB1.length>partB0.length)partB=partB1;partA=partA1elsepartB=partB0;partA=partA0


if(partB.substr(partB.length-1)=="<")partB=partB.substr(0,partB.length-1);partC="<"+partC
if(partB.substr(partB.length-2)=="</")partB=partB.substr(0,partB.length-2);partC="</"+partC

//保护textarea、xmp、script和style的内容不被改变

var cook=[]

partA=partA.replace(/(<(script|textarea|xmp|style).*?>)[\\s\\S]*?<\\/\\2>/ig,
function ()co=cook.length
cook[co]=arguments[0];return "<cook"+co+">")
var ook=""
partA=partA.replace(/(<(script|textarea|xmp|style).*?>)[\\s\\S]*?$/i,
function ()co=cook.length
ook=arguments[2]
cook[co]=arguments[0];return "<cook"+co+">")

if(ook!="")fd="(^[\\\\s\\\\S]*?<\\/"+ook+">)"
jk=new RegExp(fd,["i"])
if(jk.test(partB))jk.exec(partB)
co=cook.length
cook[co]=RegExp.$1
partB=partB.replace(jk,"<cook"+co+">")
partB=partB.replace(/(<(script|textarea|xmp|style).*?>)[\\s\\S]*?<\\/\\2>/ig,
function ()co=cook.length
cook[co]=arguments[0];return "<cook"+co+">")
ook=""
partB=partB.replace(/(<(script|textarea|xmp|style).*?>)[\\s\\S]*?$/i,
function ()co=cook.length
ook=arguments[2]
cook[co]=arguments[0];return "<cook"+co+">")

if(ook!="")fd="(^[\\\\s\\\\S]*?<\\/"+ook+">)"
jk=new RegExp(fd,["i"])
if(jk.test(partC))jk.exec(partC)
co=cook.length
cook[co]=RegExp.$1
partC=partC.replace(jk,"<cook"+co+">")
partC=partC.replace(/(<(script|textarea|xmp|style).*?>)[\\s\\S]*?<\\/\\2>/ig,
function ()co=cook.length
cook[co]=arguments[0];return "<cook"+co+">")

//处理<font>

var dol=[]
var dos=[]
var lon=[]

fd="<FONT([^>]*?)>"
jk=new RegExp(fd,["im"])
while(jk.test(partB))ce=dol.length
jk.exec(partB)
dol[ce]=RegExp.$1
partB=partB.replace(jk,"<site"+ce+">")


ce=dol.length-1
for(;ce>-1;ce--)
kjc="<site"+ce+">"
fd=kjc+\'(.*?)<\\/font>\'
jk=new RegExp(fd,["im"])
if(jk.test(partB))dos[dos.length]=ce
jk.exec(partB)
ko3=kjc+RegExp.$1+"</site"+ce+">"
partB=partB.replace(jk,ko3)
elselon[lon.length]=ce


partB=partB.replace(/<\\/font>/img,"<lonf>")

for(var c=dos.length-1;c>-1;c--)

uts=dol[dos[c]]
if(""==(uts.replace(/size=[0-7+]+/i,"").replace(/style=("|\')FONT-SIZE: [0-9.]+.*;*("|\')/im,"").replace(/[\\s\\n\\r]+/mg,"")))partB=partB.replace("<site"+dos[c]+">","")
partB=partB.replace("</site"+dos[c]+">","")elsepartB=partB.replace("<site"+dos[c]+">","<font"+uts.replace(/ size=[0-7+]+/im,"").replace(/FONT-SIZE: [0-9.]+pt/im,"").replace(/ style=("|\');*("|\')/im,"")+">")
partB=partB.replace("</site"+dos[c]+">","</font>")


//处理其他标签

var addend=""
var mio=[]

partB=partB.replace(/<([^<> ]*?) [^<>]*?style=[^<>]*?FONT-SIZE: [0-9.]+[^<>]*?>/img,
function ()notv="|select|input|option|object|"
if(notv.indexOf("|"+arguments[1].toLowerCase()+"|")==-1)
op=mio.length
mio[op]=arguments[0]
return "<opis"+op+">"elsereturn arguments[0])

kba=["h1","h2","h3","h4","h5","h6","pre","button","listing","big","small","tt","code","kbd","samp","td","\\/td","caption","\\/caption","th","\\/th","tr","\\/tr","table","\\/table","thead","\\/thead","tbody","\\/tbody","tfoot","\\/tfoot"]
for(b in kba)
fd="<("+kba[b]+")[^<>]*?>"
jk=new RegExp(fd,["img"])
partB=partB.replace(jk,
function ()op=mio.length
mio[op]=arguments[0]
return "<opis"+op+">")


//收尾工作

liming=[]

partB=partB.replace(/<(opis|site|lonf)([0-9]*)>/g,
function()var op=liming.length
if(arguments[1]=="opis")liming[op]=mio[parseInt(arguments[2])]
if(arguments[1]=="site")liming[op]="<font"+dol[parseInt(arguments[2])]+">"
if(arguments[1]=="lonf")liming[op]="</font>"
return "<duan"+op+">"
)

if(liming.length>0)
partB=partB.replace(/^(.+?)(<duan0>)/m,function()if(""!=arguments[1])return addpre+arguments[1]+"</font>"+arguments[2]
elsereturn arguments[0])

var op=liming.length-1
for(var kc=0;kc<op;kc++)
fd="(<duan"+kc+">)(.+?)(<duan"+(kc+1)+">)"
jk=new RegExp(fd,["m"])
partB=partB.replace(jk,
function()if(""!=arguments[2])return arguments[1]+addpre+arguments[2]+"</font>"+arguments[3]
elsereturn arguments[0])

fd="(<duan"+op+">)(.+?)$"
jk=new RegExp(fd,["m"])
partB=partB.replace(jk,function()if(""!=arguments[2])return arguments[1]+addpre+arguments[2]+"</font>"
elsereturn arguments[0])

elsepartB=addpre+partB+"</font>"


partB=partB.replace(/<duan([0-9]+)>/g,function()return liming[parseInt(arguments[1])])

var endtemp=partA+partB+partC

for(vof in cook)endtemp=endtemp.replace("<cook"+vof+">",cook[vof])

Editor.document.body.innerHTML=endtemp
var vrvd=Editor.document.selection.createRange()
vrvd.moveToBookmark(sBookmark)
vrvd.select()

else
Editor.document.selection.createRange().pasteHTML(addpre+"</font>")
Editor.focus()
el.blur()
el.selectedIndex=0

</script>
</head>
<body SCROLL="no" bgcolor=d0d0c8 onload="start()" leftmargin=5 topmargin=5>

<OBJECT id=dlgHelper CLASSID="clsid:3050f819-98b5-11cf-bb82-00aa00bdce0b" ></OBJECT>
<select id="select1" style="height: 18" onchange="doSelectCl(\'FormatBlock\',this);this.returnValue=false;" name="select1" size="1">
<option selected value="">段落样式</option>
<option value="P">普通
<option value="H1">标题一
<option value="H2">标题二
<option value="H3">标题三
<option value="H4">标题四
<option value="H5">标题五
<option value="H6">标题六
<option value="p">段落
<option value="dd">定义
<option value="dt">术语定义
<option value="dir">目录列表
<option value="menu">菜单列表
<option value="PRE">已编排格式
</select>
<select style="height: 18"
onchange="doSelectC(\'FontName\',this,\'font face=&quot\'+this.value+\'&quot\')"
name="selectFontName"></select></td>
<select style="width: 60; height: 18" onchange="fontsize(this)" name="selectFontSize" size="1">
<option selected>字号</option>
<option value="7">一号
<option value="6">二号
<option value="5">三号
<option value="4">四号
<option value="3">五号
<option value="2">六号
<option value="1">七号
<option value=1>1 pt
<option value=2>2 pt
<option value=3>3 pt
<option value=4>4 pt
<option value=5>5 pt
<option value=6>6 pt
<option value=7>7 pt
<OPTION value=8>8 pt
<OPTION value=9>9 pt
<OPTION value=10>10 pt
<OPTION value=11>11 pt
<OPTION value=12>12 pt
<OPTION value=13>13 pt
<OPTION value=14>14 pt
<OPTION value=15>15 pt
<OPTION value=16>16 pt
<OPTION value=17>17 pt
<OPTION value=18>18 pt
<OPTION value=19>19 pt
<OPTION value=20>20 pt
<OPTION value=21>21 pt
<OPTION value=22>22 pt
<OPTION value=23>23 pt
<OPTION value=25>25 pt
<OPTION value=28>28 pt
<OPTION value=30>30 pt
<OPTION value=35>35 pt
<OPTION value=40>40 pt
<OPTION value=45>45 pt
<OPTION value=50>50 pt
<OPTION value=60>60 pt
<OPTION value=70>70 pt
<OPTION value=85>85 pt
<OPTION value=100>100 pt
<OPTION value=120>120 pt
<OPTION value=150>150 pt
<option value=200>200 pt
<option value=250>250 pt
<option value=300>300 pt
<option value=500>500 pt
<option value=900>900 pt
<option value=2000>2000 pt</select>
<P>
<textarea name=ikj style="display:none"><P><FONT style="FONT-SIZE: 15pt"><STRONG>Bound0 子确工作室</FONT> 生物信息相关业务简介</STRONG></P><TABLE cellSpacing=0 cellPadding=0 border=0><TBODY><TR><TD><H2><FONT color=#ff0000>[多步生物信息查询的一步化]</FONT></H2><P>将繁琐的多步生物信息查询<font style=\'font-size:13pt\'>合并为一步</font>,减少在各个网页间的手工跳转。<P>只要能够理清多步生物信息查询的流程就可能做到一步化。<BR><BR><H2><FONT color=#ff0000>[批量数据转录、整合]</FONT></H2><P>将大量零散的文档收录到数据库中或整理、转化为符合要求的格式。<P>例如:将数百个结构复杂的word文档内容收录到数据库中。</P></TD><TD><CENTER>
<SCRIPT type=text/javascript>
<!--
google_ad_client = "pub-2435639364513187";
google_ad_width = 200;
google_ad_height = 90;
google_ad_format = "200x90_0ads_al_s";
google_ad_channel ="";
google_color_border = "2BA94F";
google_color_bg = "80FF00";
google_color_link = "002E3F";
google_color_text = "000000";
google_color_url = "008000";
//-->
</SCRIPT>
<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript></SCRIPT>
<SCRIPT type=text/javascript>
<!--
google_ad_client = "pub-2435639364513187";
google_ad_width = 180;
google_ad_height = 60;
google_ad_format = "180x60_as_rimg";
google_cpa_choice = "CAAQyLDUlAIaCPMwSam7wGerKMD29IMB";
google_ad_channel = "";
//-->
</SCRIPT>

<SCRIPT src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type=text/javascript></SCRIPT>
</CENTER></TD></TR></TBODY></TABLE><H2><FONT color=#ff0000>[定制Bound0 Eureka预选器数据库]</FONT></H2><P>利用Bound0 Eureka预选器数据库可以初步判断样品的成分,缩小进一步实验的处理范围,对接下来的实验环节做出调整,或预选出最佳的实验方案,从而缩短研究、开发的周期,节省人力物力。还可以初步验证某些假设推断,并有助于提出新的假设推断,构建新的假说。同时让交流和教学过程变得更简单、方便。<P>有关Bound0 Eureka预选器数据库核心技术的专利申请已被受理,专利申请号:200610077985.3。<P>定制Bound0 Eureka预选器数据库(Bound0 Eureka Preselector)的业务实质:<P>1、定制公共数据库内容的本地化整合。<BR>2、定制数据库查询方式和查询项目。<BR>3、定制分析、统计功能。<BR>4、定制数据共享功能。<BR>5、定制教学展示功能。<BR>6、定制其他辅助功能。<P>例如:Bound0酵母蛋白Eureka预选器数据库的数据内容整合了SGD数据库(Saccharomyces Genome Database,酵母基因组库<A href="http://www.yeastgenome.org/">http://www.yeastgenome.org/</A>)中的部分数据。共包含6713个蛋白的信息。<BR>Bound0酵母蛋白Eureka预选器数据库具有以下基本功能:<BR>(1)Eureka Preselector(主功能): 根据条件给出符合条件的蛋白质列表。根据蛋白质的特征与目标特征的接近程度对列表内的蛋白质进行排名。以网页形式输出、保存 Eureka 结果。对保存的结果进行对比分析。<BR>(2)以树状结构显示(treeview)蛋白质的各种生物学信息。<BR>(3)以搜索引擎形式,对描述蛋白质充当的细胞组分、参与的生物过程、分子功能等描述性特征的标准化术语(GO Term)提供注释和指导。<BR>(4)以搜索引擎形式,对蛋白质的各种ID、名称进行通译。<BR>(5)在安装了Bound0酵母蛋白Eureka预选器数据库的计算机上,实现自定义的eureka:// 协议。可以在用户自己的网页中以超级链接(文字链接、图片热点链接等)的方式动态地调用数据库中的内容进行演示。<BR>(6)自动生成上述演示所需要的链接代码。<BR>(7)独立发行(便于数据共享)的数据分析配件,可对以网页形式保存的 Eureka 结果进行处理。 </P><P><TABLE cellSpacing=0 cellPadding=0 border=0><TBODY><TR><TD><H2><FONT color=#ff0000>[定制实验人员绩效考核系统]</FONT></H2><P>将团队从落后的纸面管理中解放出来。</P></TD><TD> </TD></TR></TBODY></TABLE></P><P><FONT style="BACKGROUND-COLOR: #ff0000" color=#ffffff><STRONG>以上业务更多详情请质询</STRONG></FONT><A href="mailto:bound0@tom.com"><FONT style="BACKGROUND-COLOR: #ff0000" color=#ffffff><STRONG>bound0@tom.com</STRONG></FONT></A></P></textarea>
<iframe id="Editor" name="Editor" style="WIDTH: 100%; HEIGHT: 95%"></iframe>
</body>
参考技术A http://zhidao.baidu.com/question/16889806.html

因该有些帮助,参考资料见msnd http://msdn.microsoft.com/

参考资料:http://msdn.microsoft.com/

参考技术B 我看你问这样的问题,你还不如到网上下载一个源码呢~~多的很·~ 参考技术C 你的网站用什么语言开发的?
你要得文本编辑器必须自己开发么?
我可以给你一个开发好了,且功能比较全面的
需要的话,给我发消息。
参考技术D 去网上下一个控件不就行了,何必自己写呢,你不觉得所有这种类型的都差不多么。 第5个回答  2007-01-12 反正我不懂,留个记号以后来看答案。

以上是关于图形编辑器:对齐功能的实现的主要内容,如果未能解决你的问题,请参考以下文章

MFC图形编辑器

zabbix3.2+Grafana4.0实现可视化监控图形

怎么用matlab实现计算器功能

vi 编辑器的使用

ASP文本编辑器

使用fabricjs实现图形编辑器