// 创建一个名为 `scrawl` 的函数,它接受一个 `options` 参数,用于初始化绘图相关的配置选项。 // 如果传入了 `options` 参数,会调用 `this.initOptions(options)` 方法来进行初始化操作,这是面向对象编程中常见的构造函数初始化逻辑。 var scrawl = function (options) { options && this.initOptions(options); }; // 立即执行函数,用于创建一些私有变量,并在 `scrawl` 函数的原型对象上定义一系列方法,实现绘图及相关操作的具体功能。 // 这种方式可以将一些变量和逻辑封装在一个局部作用域内,避免全局变量污染,同时实现代码的模块化组织。 (function () { // 获取页面中 id 为 `J_brushBoard` 的 `` 元素,它作为绘图的主要画布。 // 通过 `getContext('2d')` 方法获取该画布的 2D 绘图上下文,用于后续进行图形绘制等操作。 // 创建两个变量,`drawStep` 数组用于存储绘图操作的步骤数据(用于实现撤销、重做功能),`drawStepIndex` 用于记录当前操作步骤的索引位置,作为操作步骤的指针。 var canvas = $G("J_brushBoard"), context = canvas.getContext('2d'), drawStep = [], //undo redo 存储 drawStepIndex = 0; //undo redo 指针 // 在 `scrawl` 函数的原型对象上定义一系列属性和方法,这些属性和方法会被 `scrawl` 类的实例所继承,用于实现绘图工具的各种功能,如绘图、操作按钮响应、颜色选择等。 scrawl.prototype = { // `isScrawl` 属性用于标记是否正在进行涂鸦操作,初始值为 `false`。 isScrawl: false, //是否涂鸦 // `brushWidth` 属性用于记录画笔的粗细,初始值为 `-1`,后续会根据配置或用户选择进行更新。 brushWidth: -1, //画笔粗细 // `brushColor` 属性用于记录画笔的颜色,初始值为空字符串,同样会根据配置或操作进行相应设置。 brushColor: "", //画笔颜色 // `initOptions` 方法用于初始化绘图工具的各种配置和事件监听器,接受一个 `options` 参数,包含了绘图相关的各种设置选项。 initOptions: function (options) { var me = this; // 调用 `originalState` 方法,根据传入的 `options` 初始化页面的初始状态,如画笔大小、颜色等。 me.originalState(options); // 调用 `_buildToolbarColor` 方法,根据传入的颜色列表 `options.colorList` 动态生成颜色选择区域的 HTML 结构,并添加到页面中相应的元素内。 me._buildToolbarColor(options.colorList); // 调用 `_addBoardListener` 方法,添加对画板的各种鼠标事件监听器,用于处理绘图操作,传入可撤销操作的次数 `options.saveNum`,用于控制操作记录相关逻辑。 me._addBoardListener(options.saveNum); // 调用 `_addOPerateListener` 方法,添加对操作按钮(如撤销、重做、清除画板等按钮)的点击事件监听器,传入 `options.saveNum`,用于相关操作的逻辑处理。 me._addOPerateListener(options.saveNum); // 调用 `_addColorBarListener` 方法,添加对颜色选择区域的点击事件监听器,用于处理用户选择颜色的操作。 me._addColorBarListener(); // 调用 `_addBrushBarListener` 方法,添加对画笔大小选择区域的点击事件监听器,用于处理用户选择画笔大小的操作。 me._addBrushBarListener(); // 调用 `_addEraserBarListener` 方法,添加对橡皮擦大小选择区域的点击事件监听器,用于处理用户选择橡皮擦大小的操作。 me._addEraserBarListener(); // 调用 `_addAddImgListener` 方法,添加对添加图片功能相关元素(如文件上传输入框)的事件监听器,用于处理添加背景图片的操作。 me._addAddImgListener(); // 调用 `_addRemoveImgListenter` 方法,添加对移除图片按钮的点击事件监听器,用于处理删除背景图片的操作。 me._addRemoveImgListenter(); // 调用 `_addScalePicListenter` 方法,添加对缩放图片按钮的点击事件监听器,用于处理图片缩放的操作。 me._addScalePicListenter(); // 调用 `_addClearSelectionListenter` 方法,添加对文档的鼠标移动事件监听器,用于清除选中状态,避免一些操作受选中内容的影响。 me._addClearSelectionListenter(); // 调用 `_originalColorSelect` 方法,根据传入的初始画笔颜色 `options.drawBrushColor`,初始化颜色选择区域中对应颜色的选中状态显示。 me._originalColorSelect(options.drawBrushColor); // 调用 `_originalBrushSelect` 方法,根据传入的初始画笔大小 `options.drawBrushSize`,初始化画笔大小选择区域中对应大小的选中状态显示。 me._originalBrushSelect(options.drawBrushSize); // 调用 `_clearSelection` 方法,清除页面中一些元素的可选中状态,避免误操作等情况。 me._clearSelection(); }, // `originalState` 方法用于设置绘图的初始状态,根据传入的 `options` 参数来初始化画笔宽度、颜色以及画布的一些属性,如背景颜色、线条样式等。 originalState: function (options) { var me = this; // 将当前对象的 `brushWidth` 属性设置为传入的 `options` 中的 `drawBrushSize` 值,实现画笔粗细的同步设置。 me.brushWidth = options.drawBrushSize; // 将当前对象的 `brushColor` 属性设置为传入的 `options` 中的 `drawBrushColor` 值,实现画笔颜色的同步设置。 me.brushColor = options.drawBrushColor; // 设置绘图上下文的线条宽度为当前对象的 `brushWidth` 值,确定初始画笔大小。 context.lineWidth = me.brushWidth; // 设置绘图上下文的线条颜色为当前对象的 `brushColor` 值,确定初始画笔颜色。 context.strokeStyle = me.brushColor; // 设置绘图上下文的填充颜色为透明("transparent"),即画布初始背景为透明,可能后续会根据具体绘制情况进行填充或覆盖等操作。 context.fillStyle = "transparent"; // 设置绘图上下文的线条端点样式为圆形("round"),用于去除绘制线条时的锯齿效果,使线条看起来更平滑。 context.lineCap = "round"; // 执行填充操作,由于填充颜色为透明,这里实际不会有可见的填充效果,但可能是为了确保一些默认设置生效或者后续有相关逻辑基于此操作。 context.fill(); }, // `_buildToolbarColor` 方法用于动态生成颜色选择区域的 HTML 结构,根据传入的颜色列表 `colorList` 创建一个表格形式的颜色块展示区域,并将其添加到页面中 id 为 `J_colorBar` 的元素内。 _buildToolbarColor: function (colorList) { var tmp = null, arr = []; // 创建一个表格开始标签,并将其添加到 `arr` 数组中,用于构建颜色选择区域的整体 HTML 结构。 arr.push(""); // 循环遍历传入的 `colorList` 数组,每个元素代表一种颜色代码。 for (var i = 0, color; color = colorList[i++];) { // 每 5 个颜色块为一行,当是新的一行开头(索引减 1 后能被 5 整除且不是第一个颜色块时),添加一个表格行结束标签,并添加一个新的表格行开始标签。 if ((i - 1) % 5 == 0) { if (i!= 1) { arr.push(""); } arr.push(""); } // 将颜色代码转换为完整的颜色值格式(添加 `#` 前缀),并创建一个包含该颜色作为背景色的 `` 元素,用于表示一个颜色块,添加到 `arr` 数组中。 tmp = '#' + color; arr.push(""); } // 添加表格行结束标签和表格结束标签,完成整个表格结构的构建。 arr.push("
"); // 将构建好的 HTML 结构通过设置页面中 `J_colorBar` 元素的 `innerHTML` 属性添加到页面中,实现颜色选择区域的展示。 $G("J_colorBar").innerHTML = arr.join(""); }, // `_addBoardListener` 方法用于添加对画板(`` 元素)的鼠标事件监听器,处理鼠标按下、移动、抬起和移出等操作,实现绘图功能以及操作步骤的记录,用于撤销、重做等功能实现。 _addBoardListener: function (saveNum) { var me = this, margin = 0, startX = -1, startY = -1, isMouseDown = false, isMouseMove = false, isMouseUp = false, buttonPress = 0, button, flag = ''; // 获取页面中 `J_wrap` 元素的左边距值(通过 `getComputedStyle` 方法获取计算后的样式属性值,并将其转换为整数类型),用于后续计算鼠标在画布上的实际坐标,考虑到元素的外边距等布局因素。 margin = parseInt(domUtils.getComputedStyle($G("J_wrap"), "margin-left")); // 将当前画布的初始图像数据(通过 `getImageData` 方法获取整个画布的像素数据)添加到 `drawStep` 数组中,作为操作步骤记录的起始状态,同时将 `drawStepIndex` 加 1,表示操作步骤索引前进一位。 drawStep.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height)); drawStepIndex += 1; // 使用 `domUtils`(可能是自定义的 DOM 操作工具对象,包含处理 DOM 事件绑定等功能的函数)的 `on` 方法,为 `canvas` 元素绑定多个鼠标事件("mousedown"、"mousemove"、"mouseup"、"mouseout")的监听器,在事件回调函数中根据不同的鼠标事件类型进行相应的绘图和操作记录处理。 domUtils.on(canvas, ["mousedown", "mousemove", "mouseup", "mouseout"], function (e) { button = browser.webkit? e.which : buttonPress; switch (e.type) { case 'mousedown': buttonPress = 1; flag = 1; isMouseDown = true; isMouseUp = false; isMouseMove = false; me.isScrawl = true; startX = e.clientX - margin; startY = e.clientY - margin; context.beginPath(); break; // 当鼠标按下时,记录鼠标按钮状态,设置相关操作标记为 `true`,标记当前正在进行涂鸦操作,计算鼠标在画布上的起始坐标(考虑外边距因素),并开始一个新的绘图路径,为后续绘制线条做准备。 case 'mousemove': if (!flag && button == 0) { return; } if (!flag && button) { startX = e.clientX - margin; startY = e.clientY - margin; context.beginPath(); flag = 1; } if (isMouseUp ||!isMouseDown) { return; } var endX = e.clientX - margin, endY = e.clientY - margin; context.moveTo(startX, startY); context.lineTo(endX, endY); context.stroke(); startX = endX; startY = endY; isMouseMove = true; break; // 当鼠标移动时,如果不符合某些条件(如未开始绘图或者鼠标按钮未按下等情况)则直接返回不做处理。 // 如果是正常的绘图移动操作,计算鼠标当前位置坐标,将绘图路径移动到起始坐标位置,然后绘制一条从起始坐标到当前坐标的线条,并更新起始坐标为当前坐标,标记当前正在进行鼠标移动操作,用于后续判断是否绘制连续线条等情况。 case 'mouseup': buttonPress = 0; if (!isMouseDown) return; if (!isMouseMove) { context.arc(startX, startY, context.lineWidth, 0, Math.PI * 2, false); context.fillStyle = context.strokeStyle; context.fill(); } context.closePath(); me._saveOPerate(saveNum); isMouseDown = false; isMouseMove = false; isMouseUp = true; startX = -1; startY = -1; break; // 当鼠标抬起时,重置鼠标按钮状态,如果未进行过鼠标按下操作则直接返回。 // 如果鼠标按下后没有移动过(即只是点击操作),则在点击位置绘制一个填充的圆形(根据画笔宽度作为半径),填充颜色与画笔颜色相同。 // 关闭当前绘图路径,调用 `_saveOPerate` 方法保存当前操作步骤(传入可撤销操作次数 `saveNum`),重置相关操作标记和起始坐标,完成一次绘图操作的记录。 case 'mouseout': flag = ''; buttonPress = 0; if (button == 1) return; context.closePath(); break; // 当鼠标移出画布区域时,重置相关操作标记和鼠标按钮状态,如果鼠标右键按下则直接返回,关闭当前绘图路径,避免出现一些异常的绘图状态。 } }); }, // `_addOPerateListener` 方法用于添加对操作按钮(如撤销、重做、清除画板等按钮)的点击事件监听器,根据不同按钮的点击操作执行相应的绘图状态修改、操作步骤处理等功能。 _addOPerateListener: function (saveNum) { var me = this; // 为页面中 id 为 `J_previousStep` 的元素(可能是“上一步”操作按钮)添加点击事件监听器,当点击该按钮时,执行撤销操作相关的逻辑。 domUtils.on($G("J_previousStep"), "click", function () { if (drawStepIndex > 1) { drawStepIndex -= 1; context.clearRect(0, 0, context.canvas.width, context.canvas.height); context.putImageData(drawStep[drawStepIndex - 1], 0, 0); me.btn2Highlight("J_nextStep"); drawStepIndex == 1 && me.btn2disable("J_previousStep"); } }); // 如果当前操作步骤索引大于 1(表示有可撤销的操作步骤),则将操作步骤索引减 1,清空整个画布内容(通过 `clearRect` 方法),然后将上一步的图像数据(通过 `putImageData` 方法)绘制到画布上,实现撤销操作;同时将“下一步”按钮设置为可点击状态(通过 `btn2Highlight` 方法),如果操作步骤索引回到了 1,则将“上一步”按钮设置为不可点击状态(通过 `btn2disable` 方法)。 // 为页面中 id 为 `J_nextStep` 的元素(可能是“下一步”操作按钮)添加点击事件监听器,当点击该按钮时,执行重做操作相关的逻辑。 domUtils.on($G("J_nextStep"), "click", function () { if (drawStepIndex > 0 && drawStepIndex < drawStep.length) { context.clearRect(0, 0, context.canvas.width, context.canvas.height); context.putImageData(drawStep[drawStepIndex], 0, 0); drawStepIndex += 1; me.btn2Highlight("J_previousStep"); drawStepIndex == drawStep.length && me.btn2disable("J_nextStep"); } }); // 如果当前操作步骤索引大于 0 且小于总操作步骤数(表示有可重做的操作步骤),则清空整个画布内容,将当前步骤对应的图像数据绘制到画布上,实现重做操作;同时将“上一步”按钮设置为可点击状态,当操作步骤索引达到总步骤数时,将“下一步”按钮设置为不可点击状态。 // 为页面中 id 为 `J_clearBoard` 的元素(可能是“清除画板”操作按钮)添加点击事件监听器,当点击该按钮时,执行清除画板的操作以及相关的状态重置逻辑。 domUtils.on($G("J_clearBoard"), "click", function () { context.clearRect(0, 0, context.canvas.width, context.canvas.height); drawStep = []; me._saveOPerate(saveNum); drawStepIndex = 1; me.isScrawl = false; me.btn2disable("J_previousStep"); me.btn2disable("J_nextStep"); me.btn2disable("J_clearBoard"); }); }, _addColorBarListener:function () { var me = this; domUtils.on($G("J_colorBar"), "click", function (e) { var target = me.getTarget(e), color = target.title; if (!!color) { me._addColorSelect(target); me.brushColor = color; context.globalCompositeOperation = "source-over"; context.lineWidth = me.brushWidth; context.strokeStyle = color; } }); }, _addBrushBarListener:function () { var me = this; domUtils.on($G("J_brushBar"), "click", function (e) { var target = me.getTarget(e), size = browser.ie ? target.innerText : target.text; if (!!size) { me._addBESelect(target); context.globalCompositeOperation = "source-over"; context.lineWidth = parseInt(size); context.strokeStyle = me.brushColor; me.brushWidth = context.lineWidth; } }); }, _addEraserBarListener:function () { var me = this; domUtils.on($G("J_eraserBar"), "click", function (e) { var target = me.getTarget(e), size = browser.ie ? target.innerText : target.text; if (!!size) { me._addBESelect(target); context.lineWidth = parseInt(size); context.globalCompositeOperation = "destination-out"; context.strokeStyle = "#FFF"; } }); }, _addAddImgListener:function () { var file = $G("J_imgTxt"); if (!window.FileReader) { $G("J_addImg").style.display = 'none'; $G("J_removeImg").style.display = 'none'; $G("J_sacleBoard").style.display = 'none'; } domUtils.on(file, "change", function (e) { var frm = file.parentNode; addMaskLayer(lang.backgroundUploading); var target = e.target || e.srcElement, reader = new FileReader(); reader.onload = function(evt){ var target = evt.target || evt.srcElement; ue_callback(target.result, 'SUCCESS'); }; reader.readAsDataURL(target.files[0]); frm.reset(); }); }, _addRemoveImgListenter:function () { var me = this; domUtils.on($G("J_removeImg"), "click", function () { $G("J_picBoard").innerHTML = ""; me.btn2disable("J_removeImg"); me.btn2disable("J_sacleBoard"); }); }, _addScalePicListenter:function () { domUtils.on($G("J_sacleBoard"), "click", function () { var picBoard = $G("J_picBoard"), scaleCon = $G("J_scaleCon"), img = picBoard.children[0]; if (img) { if (!scaleCon) { picBoard.style.cssText = "position:relative;z-index:999;"+picBoard.style.cssText; img.style.cssText = "position: absolute;top:" + (canvas.height - img.height) / 2 + "px;left:" + (canvas.width - img.width) / 2 + "px;"; var scale = new ScaleBoy(); picBoard.appendChild(scale.init()); scale.startScale(img); } else { if (scaleCon.style.visibility == "visible") { scaleCon.style.visibility = "hidden"; picBoard.style.position = ""; picBoard.style.zIndex = ""; } else { scaleCon.style.visibility = "visible"; picBoard.style.cssText += "position:relative;z-index:999"; } } } }); }, _addClearSelectionListenter:function () { var doc = document; domUtils.on(doc, 'mousemove', function (e) { if (browser.ie && browser.version < 11) doc.selection.clear(); else window.getSelection().removeAllRanges(); }); }, _clearSelection:function () { var list = ["J_operateBar", "J_colorBar", "J_brushBar", "J_eraserBar", "J_picBoard"]; for (var i = 0, group; group = list[i++];) { domUtils.unSelectable($G(group)); } }, _saveOPerate:function (saveNum) { var me = this; if (drawStep.length <= saveNum) { if(drawStepIndex"); } scale.innerHTML = arr.join(""); return scale; } var rect = [ //[left, top, width, height] [1, 1, -1, -1], [0, 1, 0, -1], [0, 1, 1, -1], [1, 0, -1, 0], [0, 0, 1, 0], [1, 0, -1, 1], [0, 0, 0, 1], [0, 0, 1, 1] ]; ScaleBoy.prototype = { init:function () { _appendStyle(); var me = this, scale = me.dom = _getDom(); me.scaleMousemove.fp = me; domUtils.on(scale, 'mousedown', function (e) { var target = e.target || e.srcElement; me.start = {x:e.clientX, y:e.clientY}; if (target.className.indexOf('hand') != -1) { me.dir = target.className.replace('hand', ''); } domUtils.on(document.body, 'mousemove', me.scaleMousemove); e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true; }); domUtils.on(document.body, 'mouseup', function (e) { if (me.start) { domUtils.un(document.body, 'mousemove', me.scaleMousemove); if (me.moved) { me.updateScaledElement({position:{x:scale.style.left, y:scale.style.top}, size:{w:scale.style.width, h:scale.style.height}}); } delete me.start; delete me.moved; delete me.dir; } }); return scale; }, startScale:function (objElement) { var me = this, Idom = me.dom; Idom.style.cssText = 'visibility:visible;top:' + objElement.style.top + ';left:' + objElement.style.left + ';width:' + objElement.offsetWidth + 'px;height:' + objElement.offsetHeight + 'px;'; me.scalingElement = objElement; }, updateScaledElement:function (objStyle) { var cur = this.scalingElement, pos = objStyle.position, size = objStyle.size; if (pos) { typeof pos.x != 'undefined' && (cur.style.left = pos.x); typeof pos.y != 'undefined' && (cur.style.top = pos.y); } if (size) { size.w && (cur.style.width = size.w); size.h && (cur.style.height = size.h); } }, updateStyleByDir:function (dir, offset) { var me = this, dom = me.dom, tmp; rect['def'] = [1, 1, 0, 0]; if (rect[dir][0] != 0) { tmp = parseInt(dom.style.left) + offset.x; dom.style.left = me._validScaledProp('left', tmp) + 'px'; } if (rect[dir][1] != 0) { tmp = parseInt(dom.style.top) + offset.y; dom.style.top = me._validScaledProp('top', tmp) + 'px'; } if (rect[dir][2] != 0) { tmp = dom.clientWidth + rect[dir][2] * offset.x; dom.style.width = me._validScaledProp('width', tmp) + 'px'; } if (rect[dir][3] != 0) { tmp = dom.clientHeight + rect[dir][3] * offset.y; dom.style.height = me._validScaledProp('height', tmp) + 'px'; } if (dir === 'def') { me.updateScaledElement({position:{x:dom.style.left, y:dom.style.top}}); } }, scaleMousemove:function (e) { var me = arguments.callee.fp, start = me.start, dir = me.dir || 'def', offset = {x:e.clientX - start.x, y:e.clientY - start.y}; me.updateStyleByDir(dir, offset); arguments.callee.fp.start = {x:e.clientX, y:e.clientY}; arguments.callee.fp.moved = 1; }, _validScaledProp:function (prop, value) { var ele = this.dom, wrap = $G("J_picBoard"); value = isNaN(value) ? 0 : value; switch (prop) { case 'left': return value < 0 ? 0 : (value + ele.clientWidth) > wrap.clientWidth ? wrap.clientWidth - ele.clientWidth : value; case 'top': return value < 0 ? 0 : (value + ele.clientHeight) > wrap.clientHeight ? wrap.clientHeight - ele.clientHeight : value; case 'width': return value <= 0 ? 1 : (value + ele.offsetLeft) > wrap.clientWidth ? wrap.clientWidth - ele.offsetLeft : value; case 'height': return value <= 0 ? 1 : (value + ele.offsetTop) > wrap.clientHeight ? wrap.clientHeight - ele.offsetTop : value; } } }; })(); //后台回调 function ue_callback(url, state) { var doc = document, picBorard = $G("J_picBoard"), img = doc.createElement("img"); //图片缩放 function scale(img, max, oWidth, oHeight) { var width = 0, height = 0, percent, ow = img.width || oWidth, oh = img.height || oHeight; if (ow > max || oh > max) { if (ow >= oh) { if (width = ow - max) { percent = (width / ow).toFixed(2); img.height = oh - oh * percent; img.width = max; } } else { if (height = oh - max) { percent = (height / oh).toFixed(2); img.width = ow - ow * percent; img.height = max; } } } } //移除遮罩层 removeMaskLayer(); //状态响应 if (state == "SUCCESS") { picBorard.innerHTML = ""; img.onload = function () { scale(this, 300); picBorard.appendChild(img); var obj = new scrawl(); obj.btn2Highlight("J_removeImg"); //trace 2457 obj.btn2Highlight("J_sacleBoard"); }; img.src = url; } else { alert(state); } } //去掉遮罩层 function removeMaskLayer() { var maskLayer = $G("J_maskLayer"); maskLayer.className = "maskLayerNull"; maskLayer.innerHTML = ""; dialog.buttons[0].setDisabled(false); } //添加遮罩层 function addMaskLayer(html) { var maskLayer = $G("J_maskLayer"); dialog.buttons[0].setDisabled(true); maskLayer.className = "maskLayer"; maskLayer.innerHTML = html; } //执行确认按钮方法 function exec(scrawlObj) { if (scrawlObj.isScrawl) { addMaskLayer(lang.scrawlUpLoading); var base64 = scrawlObj.getCanvasData(); if (!!base64) { var options = { timeout:100000, onsuccess:function (xhr) { if (!scrawlObj.isCancelScrawl) { var responseObj; responseObj = eval("(" + xhr.responseText + ")"); if (responseObj.state == "SUCCESS") { var imgObj = {}, url = editor.options.scrawlUrlPrefix + responseObj.url; imgObj.src = url; imgObj._src = url; imgObj.alt = responseObj.original || ''; editor.execCommand("insertImage", imgObj); dialog.close(); } else { alert(responseObj.state); } } }, onerror:function () { alert(lang.imageError); dialog.close(); } }; options[editor.getOpt('scrawlFieldName')] = base64; var actionUrl = editor.getActionUrl(editor.getOpt('scrawlActionName')), params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '', url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + params); ajax.request(url, options); } } else { addMaskLayer(lang.noScarwl + "   "); } }