图片
tinymce 提供了丰富的图片上传、管理、修改功能。只需要配置对应插件及一些参数即可。
图片上传
引用image
插件,添加images_upload_handler
配置项,图片弹窗左侧导航会多一个上传的选项,在点击之后即可完成我们常用的图片上传功能
<template>
<div class="default-tinymce">
<textarea id="editor"></textarea>
</div>
</template>
<script>
import Tinymce from 'tinymce'
import Upload from 'src/utils/upload'
export default {
name: 'DefaultTinymce',
mounted () {
this.upload = new Upload()
let self = this
Tinymce.init({
selector: '#editor',
plugins: 'image',
async images_upload_handler (blobInfo, success, fail) {
const file = blobInfo.blob()
try {
const url = self.YB.businessURL(await self.upload.post(file))
success(url)
} catch (e) {
fail(e.message || '上传失败,请重试')
}
}
})
}
}
</script>
图片管理
tinymce提供了类似个人图库和公共图库的功能。使用起来也很方便,只要配置即可。
这个我们暂时没有这个需求,以后可能会用到。
一种是下拉选择,配置image_list
即可,值得注意的是,它的值可以是数组,函数,或者接口。
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "image",
image_list: [
{title: 'Dog', value: 'mydog.jpg'},
{title: 'Cat', value: 'mycat.gif'}
]
});
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "image",
image_list: "/mylist.php"
});
tinymce.init({
selector: "textarea", // change this value according to your HTML
plugins: "image",
image_list: function(success) {
success([
{title: 'Dog', value: 'mydog.jpg'},
{title: 'Cat', value: 'mycat.gif'}
]);
}
});
另一种是提供了一个按钮,点击这个按钮会触发相应的方法file_picker_callback
,方法的参数中有回调函数,我们可以利用这个方法做一个个人图库。
tinymce.init({
selector: 'textarea', // change this value according to your HTML
file_picker_callback: function(callback, value, meta) {
// 以下是伪代码
// 打开一个弹窗
openDialog()
// 展示图片列表
showImageList()
// 选择图片,确认选择
const url = submitActiveImage()
// 关闭弹窗,返回图片
callback(url)
}
});
图片编辑
图片编辑分为两部分,
一部分是对图片属性的编辑,比如边框、边距、宽度等,这部分其实就是对插件image
的配置;
一部分是对图片自身的编辑,比如剪裁、调色等。这部分是基于插件image Tools
的配置
image
image_caption
- 默认值
false
- 可能值
[true/false]
图片下方增加对图片的描述文字。开启后,会在弹窗面板多一个显示标题的可选项。
config = {
image_caption: true
}
这会生成新的结构
<figure class="image" contenteditable="false" data-mce-selected="1">
<img src="https://imgtest.120yibao.com/test/base/obu1oj9a5fhy4ftku49.jpeg" alt="sdf" width="650" height="639">
<figcaption contenteditable="true">图片底部</figcaption>
</figure>
image_class_list
- 值类型
string
可以下拉选择给图片加对应class
config = {
image_class_list: [
{ title: '无', value: '' },
{ title: '自适应屏幕宽度', value: 'adaptive-screen-width' }
]
}
image_advtab
- 默认值
false
- 可能值
[true/false]
图标编辑弹窗多了一个属性编辑的tab,包含外边距(margin
),边框(border
)的编辑。修改后,图片的style
的属性值会被覆盖。
image_description
- 默认值
true
- 可能值
[true/false]
对应img
标签的alt
属性
image_dimensions
- 默认值
true
- 可能值
[true/false]
是否可以在弹窗中通过input
设置图片的宽高
image Tools
此插件会编辑图片的原始数据。
编辑后自动上传
涉及修改图片原始数据的操作(如剪裁、翻转等),需要重新上传图片。可以配合服务端接口重新上传,也可以前端间接调用images_upload_handler
方法进行上传。
我们看一下前端上传
function handleImageUrlToBlob (url, callback) {
function imageToCanvas (src, cb) {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const img = new Image()
img.src = src + '?t=2'
img.crossOrigin = ''
img.onload = function () {
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
cb(canvas)
}
}
function dataURLToBlob (dataURL) {
const arr = dataURL.split(',')
const mime = arr[0].match(/:(.*?);/)[1]
const bStr = atob(arr[1])
let n = bStr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bStr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}
function canvasToDataURL (canvas, format, quality) {
return canvas.toDataURL(format || 'image/jpeg', quality || 1.0)
}
imageToCanvas(url, function (canvas) {
callback(dataURLToBlob(canvasToDataURL(canvas)))
})
}
tinymce.init({
// 此两项在使用imagetools插件时必填,只在使用服务端上传时有用。但前端使用了其他方法获取图片(imagetools_fetch_image),所以随便写了些值
imagetools_cors_hosts: [],
imagetools_proxy: 'just a string and do nothing',
// 修改图片之后,获取图片
imagetools_fetch_image: image => {
return new tinymce.util.Promise(function (resolve) {
// 需要将图片转为blob格式
handleImageUrlToBlob(image.src, resolve)
})
},
})
修改完图片之后,编辑器会在图片失去焦点之后自动上传图片,并生成新的URL替换掉之前的。
编辑功能列表配置
imagetools_toolbar
可配置编辑功能列表
tinymce.init({
selector: "textarea",
toolbar: "image",
plugins: "image imagetools",
imagetools_toolbar: "rotateleft rotateright | flipv fliph | editimage imageoptions"
});
格式化
有时候你需要对内容进行一些格式化,例如改变字体大小、将内容转换为标题,或者直接清除选择内容的所有样式。这不需要引入插件,至于要配置即可。
tinymce.init({
selector: "textarea",
/**
* 「块」样式格式化
*/
block_formats: '段落=p; 标题 1=h1; 标题 2=h2; 标题 3=h3; 标题 4=h4; 标题 5=h5; 标题 6=h6;',
/**
* 工具栏「段落」下拉组件的默认值
*/
style_formats: [
{ title: 'Headings',
items: [
{ title: 'Heading 1', format: 'h1' },
{ title: 'Heading 2', format: 'h2' },
{ title: 'Heading 3', format: 'h3' },
{ title: 'Heading 4', format: 'h4' },
{ title: 'Heading 5', format: 'h5' },
{ title: 'Heading 6', format: 'h6' }
] },
{ title: 'Inline',
items: [
{ title: 'Bold', format: 'bold' },
{ title: 'Italic', format: 'italic' },
{ title: 'Underline', format: 'underline' },
{ title: 'Strikethrough', format: 'strikethrough' },
{ title: 'Superscript', format: 'superscript' },
{ title: 'Subscript', format: 'subscript' },
{ title: 'Code', format: 'code' }
] },
{ title: 'Blocks',
items: [
{ title: 'Paragraph', format: 'p' },
{ title: 'Blockquote', format: 'blockquote' },
{ title: 'Div', format: 'div' },
{ title: 'Pre', format: 'pre' }
] },
{ title: 'Align',
items: [
{ title: 'Left', format: 'alignleft' },
{ title: 'Center', format: 'aligncenter' },
{ title: 'Right', format: 'alignright' },
{ title: 'Justify', format: 'alignjustify' }
] }
],
/**
* 字体大小可选列表
*/
fontsize_formats: '12px 14px 16px 18px 24px 36px 48px',
/**
* 格式化
*/
formats: {
// 清除格式
removeformat: [
{
selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins',
remove: 'all',
split: true,
block_expand: true,
expand: false,
deep: true
},
{
selector: 'span',
attributes: ['style', 'class'],
remove: 'empty',
split: true,
expand: false,
deep: true },
{
selector: '*',
attributes: ['style', 'class'],
split: false,
expand: false,
deep: true
}
]
}
});
转换外链图片
如果粘贴过来的内容中有图片,并且图片是外链图片。可能需要把这些外链图片转换成自己服务器的地址,这需要后端配合:给后端这些图片的url,后端返回给你自己的url,由后端完成图片上传。
如果粘贴内容中的图片是base64,会自动触发之前配置的images_upload_handler
图片上传钩子,自动上传图片。
这一块的实现比较复杂,我不贴代码,只说下大致思路:
- 在
paste_postprocess
方法中标注这些外链图片 - 然后监听编辑器的内容插入事件(
mceInsertContent
),在事件的回调方法中,将这些标注的外链图片进行转化。
需要注意的是,因为一些图片的提供者会对图片进行保护,服务器不一定你传过去的每张图片都能传到自己的服务器,需要和后端约定处理方法。
封装成Vue组件
首先,我们要实现Vue的双向绑定:在编辑器内容改变的时候,更新ViewModel;ViewModel改变的时候,更新编辑器的内容。
实现起来很简单,我们可以监听可以改变编辑器内容的事情,以及使用vm.$watch
监听值的变化。
<template>
<div class="editor-wrap">
<textarea v-model="value" class="editor-textarea"></textarea>
</div>
</template>
<script>
export default {
name: 'TinymceEditor',
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: String,
required: true,
default: ''
}
},
async mounted () {
try {
const self = this
const editor = await Tinymce.init({
selector: '.editor-textarea',
// 编辑器实例初始化完成的回调
init_instance_callback: editor => {
// 数据双向绑定
self.$nextTick(() => {
let currentContent = ''
// 双向绑定数据
self.$watch('value', (val, prevVal) => {
if (editor && typeof val === 'string' && val !== currentContent && val !== prevVal) {
editor.setContent(val)
currentContent = val
}
})
editor.on('change keyup undo redo', () => {
currentContent = editor.getContent()
self.$emit('change', currentContent)
})
})
}
})
this.editor = editor[0]
} catch (e) {
this.$error(e)
}
}
}
</script>