这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战
文本编辑
我们之前说过,Konva
并没有TextBox
组件,而Text
组件默认是不支持编辑功能,所以我们使用Konva
没有办法添加文本框组件,不过我们可以通过DOM
的方式来处理这个问题.
具体的思路,应该就是在双击文本组件的时候,我们在我们组件上创建一个DOM
的输入框组件放置在文本组件对应位置的上层,当结束输入时,就销毁输入框组件,我们可以从输入框组件中获取文本值来更新显示的文本组件.
我们认为按下回车
和输入框失去焦点
两个事件是结束输入状态的标志.
/**
* 创建编辑区域
* @param node
*/
function createTextarea(node: Konva.Text) {
const layer = node.getLayer()
const stage = layer.getLayer()
// 获取文本位置
const areaPosition = {
x: stage.offsetX() + node.absolutePosition().x,
y: stage.offsetY() + node.absolutePosition().y
}
// 创建TextArea
const textarea = document.createElement('textarea')
const container = document.getElementById('canvas-container')
container.appendChild(textarea)
textarea.value = node.text()
textarea.style.position = 'absolute'
textarea.style.top = areaPosition.y + 'px'
textarea.style.left = areaPosition.x + 'px'
textarea.style.width = `${node.width() - node.padding() * 2}px`
textarea.style.height = `${node.height() - node.padding() * 2 + 5}px`
textarea.style.fontSize = `${node.fontSize()}px`
textarea.style.border = 'none'
textarea.style.padding = '0px'
textarea.style.margin = '0px'
textarea.style.overflow = 'hidden'
textarea.style.background = 'none'
textarea.style.outline = 'none'
textarea.style.resize = 'none'
textarea.style.wordBreak = 'break-all'
textarea.style.fontFamily = node.fontFamily()
textarea.style.transformOrigin = 'left top'
textarea.style.textAlign = node.align()
textarea.style.color = node.fill()
textarea.style.transform = `rotateZ(${node.rotation()}deg) scale(${layer.scaleX()})`
textarea.focus()
/**
* 移除Textarea
*/
function removeTextarea() {
textarea.parentNode.removeChild(textarea)
node.show()
}
textarea.addEventListener('keydown', function (e) {
if (e.key === 'Enter' && !e.shiftKey) {
textarea.blur()
}
})
textarea.addEventListener('blur', function (e) {
node.text(textarea.value)
node.fire(TextWidgetEvent.input)
removeTextarea()
})
}
可以发现我们从文本组件中获取属性,通过配置输入框的style
来让他们保持一致的样式,这样可以在用户双击时减少视觉上的变化.
组件的缩放比和角度通过style.transform
来进行更新,我们使用blur
和keydown
事件来确认结束输入的时机.
这样我们在创建文本组件时就可以来添加文本编辑支持.
// 添加编辑功能
textNode.on('dblclick dbltap', () => {
// 隐藏原节点
node.hide()
// 获取文本位置
createTextarea(node)
})
事件监听
我们为了保证UI上显示和数据的同步,我们通过监听组件属性的修改来同步修改我们保存的组件数据.
组件可以通过on
来监听对应的组件事件,我们需要来监听的事件有:
- transformend
- dragend
transformend
是选择框拖动锚点产生的事件,当监听到transformed
事件时,我们需要修改如下属性
- x
- y
- width
- height
- scaleX
- scaleY
- rotation
dragend
是组件拖动事件,他会影响组件的x
,y
值
我们也可以通过node.fire
来自定义我们需要的事件,比如文本框输入时需要修改存储的文本值.
node.fire('input')
node.on('input', () => {
// 更新文本值
})
导出图片
我们可以通过stage
的toDataUrl
可以导出图片的base64
编码,然后我们就可以使用a
标签实现下载功能
不过在之前我们需要先通过我们之前设置缩放比来计算输出的比例和尺寸
const { content: contentLayer } = getLayers()
const width =
(contentLayer.clip().width / appConfig.editor.content.scale) * $zoom
const height =
(contentLayer.clip().height / appConfig.editor.content.scale) * $zoom
因为我们不希望导出整个舞台,而只是希望导出其中内容区域的部分,所以x
,y
应该使用contentLayer
的x
,y
值
const dataURL = $stage.toDataURL({
width,
height,
x: contentLayer.x(),
y: contentLayer.y(),
pixelRatio: appConfig.editor.content.scale,
quality: 1
})
现在我们获得了可以用来下载的dataURL
,就可以通过a
标签直接进行下载了
const download = (uri, name) => {
const link = document.createElement('a')
link.download = name
link.href = uri
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
download(dataURL, `${Date.now()}.png`)
但是在生成过程中也常常会遇见一种问题
Konva error: Unable to get data URL. Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.
因为图片跨域会在导出的时候发生报错.
一种解决办法是可以尝试设置跨域标记
img.crossOrigin = "anonymous"
或者也可以尝试直接获取图片的base64编码来设置src
来解决
export async function getBase64FromUrl(url) {
const timespan = Math.random().toString(32).slice(2)
const data = await fetch(url + `?v=${timespan}`)
const blob = await data.blob()
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = () => {
const base64data = reader.result
resolve(base64data)
}
})
}
此外还有遇到一种情况,就是第一次获取图片是正常,第二次再打开就会报跨域错误.这是因为第一次找服务器请求,服务器返回了跨域头信息,而第二次图片被缓存了,直接走了浏览器缓存后没有了跨域头信息,所以产生了跨域错误.这是需要在图片路径上加一个随机时间戳来防止读取缓存数据就可以了.
img.src = `url?timestamp=${Date.now()}`
源码地址: github
如果您觉得这篇文章有帮助到您的的话不妨🍉关注+点赞+收藏+评论+转发🍉支持一下哟~~😛