最近项目中h5端要实现图文上传,而且还要支持用户用户输入的格式,例如换行啥的,那么使用输入控件保存输入内容,图片上传控件就不合适了,因为很难知道用户的输入样式。
如果使用一些现有的富文本编辑器,貌似又不是很划算,所以综合考虑使用div来自己实现一个就是比较理想的方案了。
先来考虑一下,如果使用div来实现简单的富文本编辑器,需要解决哪些问题?
首先,div默认是不可编辑的,需要设置它可以编辑,这个很简单只需要使用
contentEditable="true"
其次,我们需要在div指定的位置插入图片,指定位置就是光标所在的位置,那么我们插入图片的时候,需要知道光标的位置,网上查了下应该是需要getSelection API W3Cschool上面直接有简单的用法,感兴趣的童鞋可以去看看。
获取当前光标选中位置
let selection = window.getSelection();
let range = selection.getRangeAt(0);
那么知道了光标位置,怎么插入图片?
let img = document.createElement("img");
img.src = json.url;
//最大宽度为手机宽度减去20
img.style.maxWidth = (width-20) + 'px';
range.insertNode(img);//选中位置插入图片
这就完了?
当然还没完,实现中遇到了另一个问题,那就是焦点的变化。如果我想插入图片,我就首先需要上传图片,但是当我点击上传图片的按钮的时候,焦点就已经发生了变化,这个时候获取的选中位置就是你点击的位置!
那么该如何解决这个问题呢?
仔细分析,点击的时候,焦点变化的时候,我们需要记录下上次焦点的位置,上次焦点的位置在哪里?在可编辑的div中,焦点变化的时候,就是可编辑div失去焦点的时候!
那么是否可以监听div失焦这个事件呢?答案当然是的!
onBlur={() => {
let selection = window.getSelection();
range = selection.getRangeAt(0);
}}
给div实现onBlur监听就可以了,是不是很简单?
接下来看一下完整的代码
import React, {Component} from "react";
import "../../assets/css/topic/DivEdit.css";
import "../../assets/common/second-common.css";
let range;
let width = document.documentElement.clientWidth;
//头部栏
class DivEdit extends Component {
constructor(props) {
super(props);
let typeArr = [".png", ".jpg", ".jpeg", ".bmp", ".gif"];
this.state = {
id:'',
inputValueHtml: '',
fileType: typeArr,
showTips:true
}
}
//光标定位在可编辑div的开始的位置
setStartFocus() {
document.querySelector('#my-question-define-edit').focus();
}
//光标定位在内容的尾端
setEndFocus() {
let srcObj = document.querySelector('#my-question-define-edit');
let selection = window.getSelection();
range = document.createRange();
range.selectNodeContents(srcObj);
selection.removeAllRanges();
selection.addRange(range);
range.setStart(srcObj, 1);
range.setEnd(srcObj, 1);
}
// 调用input 选择文件
triggerSelect() {
return document.getElementById('topic-add-img').click();
}
//处理上传文件
handleChange(file) {
// 判断文件类型进行上传
if (window.typeMatch(this.state.fileType, file.name)) {
// 上传文件
this.saveFile(file);
} else {
Toast.fail("文件类型不符,请重新选择", 3);
}
}
// 上传文件
saveFile(file) {
//你们自己处理上传的处理 //当成功的时候
let img = document.createElement("img");
img.src = json.url;
img.style.maxWidth = (width-20) + 'px';
if(range) {
range.insertNode(img);
}else{
this.setStartFocus();
let selection = window.getSelection();
range = selection.getRangeAt(0);
range.insertNode(img);
}
}
componentDidMount() {
let question=this.props.question;
this.setState({
inputValueHtml: '<p>' + question + '</p><br/><br/><br/>',showTips:!(question&&question!=='')
},
() => {
if(question&&question!==''){
this.setEndFocus();
}
});
}
getHtml(){
return this._editDiv.innerHTML;
}
render() {
return (
<div className={'add-my-question-first-edit'} >
<div id={'my-question-define-edit'}
onFocus={() => {
this.setState({showTips:false});
}}
onBlur={() => {
let selection = window.getSelection();
range = selection.getRangeAt(0);
}}
onClick={() => {
let selection = window.getSelection();
range = selection.getRangeAt(0);
}}
ref={(editDiv) => this._editDiv = editDiv}
className={'add-my-question-edit'}
contentEditable="true"
dangerouslySetInnerHTML={{__html: this.state.inputValueHtml}}/>
{
this.state.showTips?
<span style={{
position: 'absolute',
top:10,
left:10,
marginTop:'1.333rem',
fontSize:17,color:'#E8E8E8'
}}>输入您的问题...</span>:null
}
<div className={'add-bottom-menu'}>
<img style={{width: 20, position: 'absolute', left: 10}}
src={require("../../assets/images/topic_topic_show.png")}
alt='箭头'/>
<div className={'topic-add-pic'}
onClick={() => {
//调起上传图片
this.triggerSelect();
}}>
<img style={{width: 20}}
src={require("../../assets/images/topic_upload_pic.png")}
alt='上传图片'/>
<span style={{marginLeft: 10, fontSize: 15}}>上传图片</span>
</div>
</div>
<div style={{visibility: 'hidden'}}>
<input
type="file"
id={'topic-add-img'}
style={{display: "none"}}
onChange={e => this.handleChange(e.target.files[0])}
/>
</div>
</div>
);
}
}
export default DivEdit;
样式表
.add-my-question-first-edit {
display: flex;
flex-direction: column;
flex: 1;
}
.add-my-question-first-edit .add-my-question-edit{
flex: 1;
margin-top: 1.333rem;
/*margin-bottom: 1.333rem;*/
font-size: 0.453rem;
background-color: #fff;
padding: 10px 10px 1.867rem;
}
.add-my-question-first-edit .add-bottom-menu{
position: fixed;
bottom: 0;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 1.067rem;
background-color: #fff;
border-top: #E8E8E8 solid 1px;
}
.add-my-question-first-edit .add-bottom-menu .topic-add-pic{
display: flex;
flex-direction: row;
align-items: center;
align-self: center;
}
更多内容,欢迎同步关注作者二维码!