功能与场景
在项目中会有文章内容或其他什么东西是后台富文本编辑的,需要前端展示富文本内容
正常来说是没什么问题的,但是总有客户喜欢上传1k+ 甚至是2k+ 像素的图片或视频,这就导致在移动端中渲染时无法完整的渲染出图片/视频
如下图
所以就封装了这个处理富文本中的媒体元素尺寸的功能
功能实现:
- 富文本中媒体元素的宽度自适应当前设备的宽度
- 如果媒体元素的宽度小于等于当前设备的宽度则不处理宽度
- 如果媒体元素的宽度大于当前设备的宽度则进行等比例缩小
- 如果媒体元素指定了高度,并且宽度大于当前设备的宽度,高度也会随着宽度的缩小比例进行相应的缩小,从而实现媒体元素等比例缩小
处理后效果如下图
代码
/**
* 处理富文本中的媒体元素尺寸
* @param {String} content 富文本内容
* @param {Object} proxy 页面/组件实例
* @param {String} symbol 富文本内容的父级元素选择器(例如:.class、#id)
* @returns {String} 处理后的富文本内容
*/
export const handleRichText = async function (content, proxy, symbol) {
// 获取content元素的宽度
const query = uni.createSelectorQuery().in(proxy);
const boxInfo = await new Promise((resolve) => {
query
.select(symbol)
.boundingClientRect((data) => {
resolve(data);
})
.exec();
});
let containerWidth = boxInfo.width;
console.log('容器信息:', {
选择器: symbol,
容器宽度: containerWidth + 'px',
容器完整信息: boxInfo,
});
// px 转 rpx 的比例
const pxToRpxRatio = 2;
// 处理图片
content = content.replace(/<img[^>]*\/?\s*>/gi, (match) => {
// 合并 style 和 data-original-style
const styleMatch = match.match(/style="([^"]*)"/);
const dataOriginalStyleMatch = match.match(/data-original-style="([^"]*)"/);
let mergedStyle = '';
if (styleMatch) mergedStyle += styleMatch[1] + ';';
if (dataOriginalStyleMatch) mergedStyle += dataOriginalStyleMatch[1] + ';';
// 提取宽度和高度(优先级:mergedStyle > width/height 属性 > data-width/data-height)
let width, height;
// 1. mergedStyle
let widthStyleMatch = mergedStyle.match(/width:\s*([\d.]+)px/);
let heightStyleMatch = mergedStyle.match(/height:\s*([\d.]+)px/);
if (widthStyleMatch) width = parseFloat(widthStyleMatch[1]);
if (heightStyleMatch) height = parseFloat(heightStyleMatch[1]);
// 2. width/height 属性
if (!width) {
const attrWidthMatch = match.match(/\swidth="([\d.]+)"/);
if (attrWidthMatch) width = parseFloat(attrWidthMatch[1]);
}
if (!height) {
const attrHeightMatch = match.match(/\sheight="([\d.]+)"/);
if (attrHeightMatch) height = parseFloat(attrHeightMatch[1]);
}
// 3. data-width/data-height
if (!width) {
const dataWidthMatch = match.match(/data-width="([\d.]+)px"/);
if (dataWidthMatch) width = parseFloat(dataWidthMatch[1]);
}
if (!height) {
const dataHeightMatch = match.match(/data-height="([\d.]+)px"/);
if (dataHeightMatch) height = parseFloat(dataHeightMatch[1]);
}
// 提取图片地址
const srcMatch = match.match(/src="([^"]*)"/);
const imgSrc = srcMatch ? srcMatch[1] : '未知图片地址';
console.log('处理图片(优化):', {
图片地址: imgSrc,
原始标签: match,
合并style: mergedStyle,
宽度: width,
高度: height,
});
let newStyle = '';
if (width) {
let finalWidth = width;
let finalHeight = height;
let ratio = 1;
if (width > containerWidth) {
ratio = containerWidth / width;
finalWidth = containerWidth;
if (height) finalHeight = height * ratio;
}
const widthRpx = Math.floor(finalWidth * pxToRpxRatio);
newStyle += `width: ${widthRpx}rpx; max-width: 100% !important;`;
if (finalHeight) {
const heightRpx = Math.floor(finalHeight * pxToRpxRatio);
newStyle += ` height: ${heightRpx}rpx;`;
}
} else {
// 没有宽度,强制最大宽度
newStyle = 'max-width: 100% !important; height: auto;';
}
// 移除所有 width/height 属性、style 里的宽高
let processedMatch = match
.replace(/\swidth="[^"]*"/gi, '')
.replace(/\sheight="[^"]*"/gi, '')
.replace(/style="[^"]*"/gi, '')
.replace(/data-original-style="[^"]*"/gi, '');
// 添加新的 style
if (/\s*\/?>$/.test(processedMatch)) {
// 自闭合标签
processedMatch = processedMatch.replace(/\s*\/?>$/, ` style="${newStyle}"/>`);
} else {
processedMatch = processedMatch.replace(/>$/, ` style="${newStyle}">`);
}
console.log('图片处理结果(优化):', {
图片地址: imgSrc,
处理后标签: processedMatch,
处理后style: newStyle,
});
return processedMatch;
});
// 处理视频
content = content.replace(/<video[^>]*\/?\s*>/gi, (match) => {
// 合并 style 和 data-original-style
const styleMatch = match.match(/style="([^"]*)"/);
const dataOriginalStyleMatch = match.match(/data-original-style="([^"]*)"/);
let mergedStyle = '';
if (styleMatch) mergedStyle += styleMatch[1] + ';';
if (dataOriginalStyleMatch) mergedStyle += dataOriginalStyleMatch[1] + ';';
// 提取宽度和高度(优先级:mergedStyle > width/height 属性 > data-width/data-height)
let width, height;
// 1. mergedStyle
let widthStyleMatch = mergedStyle.match(/width:\s*([\d.]+)px/);
let heightStyleMatch = mergedStyle.match(/height:\s*([\d.]+)px/);
if (widthStyleMatch) width = parseFloat(widthStyleMatch[1]);
if (heightStyleMatch) height = parseFloat(heightStyleMatch[1]);
// 2. width/height 属性
if (!width) {
const attrWidthMatch = match.match(/\swidth="([\d.]+)"/);
if (attrWidthMatch) width = parseFloat(attrWidthMatch[1]);
}
if (!height) {
const attrHeightMatch = match.match(/\sheight="([\d.]+)"/);
if (attrHeightMatch) height = parseFloat(attrHeightMatch[1]);
}
// 3. data-width/data-height
if (!width) {
const dataWidthMatch = match.match(/data-width="([\d.]+)px"/);
if (dataWidthMatch) width = parseFloat(dataWidthMatch[1]);
}
if (!height) {
const dataHeightMatch = match.match(/data-height="([\d.]+)px"/);
if (dataHeightMatch) height = parseFloat(dataHeightMatch[1]);
}
// 提取视频地址
const srcMatch = match.match(/src="([^"]*)"/);
const videoSrc = srcMatch ? srcMatch[1] : '未知视频地址';
console.log('处理视频(优化):', {
视频地址: videoSrc,
原始标签: match,
合并style: mergedStyle,
宽度: width,
高度: height,
});
let newStyle = '';
if (width) {
let finalWidth = width;
let finalHeight = height;
let ratio = 1;
if (width > containerWidth) {
ratio = containerWidth / width;
finalWidth = containerWidth;
if (height) finalHeight = height * ratio;
}
const widthRpx = Math.floor(finalWidth * pxToRpxRatio);
newStyle += `width: ${widthRpx}rpx; max-width: 100% !important;`;
if (finalHeight) {
const heightRpx = Math.floor(finalHeight * pxToRpxRatio);
newStyle += ` height: ${heightRpx}rpx;`;
}
} else {
// 没有宽度,强制最大宽度
newStyle = 'max-width: 100% !important; height: auto;';
}
// 移除所有 width/height 属性、style 里的宽高
let processedMatch = match
.replace(/\swidth="[^\"]*"/gi, '')
.replace(/\sheight="[^\"]*"/gi, '')
.replace(/style="[^"]*"/gi, '')
.replace(/data-original-style="[^"]*"/gi, '');
// 添加新的 style
if (/\s*\/?>$/.test(processedMatch)) {
// 自闭合标签
processedMatch = processedMatch.replace(/\s*\/?>$/, ` style="${newStyle}"/>`);
} else {
processedMatch = processedMatch.replace(/>$/, ` style="${newStyle}">`);
}
console.log('视频处理结果(优化):', {
视频地址: videoSrc,
处理后标签: processedMatch,
处理后style: newStyle,
});
return processedMatch;
});
return content;
};
使用
因为我是将其封装成一个工具函数,所以在页面导入函数使用即可
示例代码:
<div class="content">
<rich-text :nodes="detail.content"></rich-text>
</div>
import { getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
import { handleRichText } from '@/utils/index';
async function getDetail() {
try {
const res = await $getArticleDetail({ id: id.value });
res.content = await handleRichText(res.content, proxy, '.content');
detail.value = res;
} catch (error) {
console.log(error);
}
}
注意:
- 在页面中需要给富文本渲染组件添加一个父元素,这样富文本渲染的媒体文件的宽度就不会超过父元素的宽度了
- 需要传入富文本渲染组件父元素的元素选择器,这是为了获取父元素的宽度,从而限制富文本媒体元素的宽度/高度
- 该方法是一个异步方法,所以需要使用await或then来等待函数处理完成