feat: make @idraw/core support html element

This commit is contained in:
chenshenhai 2021-07-26 15:56:28 +08:00
parent 22a8a360f1
commit 796833d138
13 changed files with 217 additions and 27 deletions

View file

@ -0,0 +1,89 @@
const data = {
// bgColor: '#ffffff',
elements: [
{
name: "html-001",
x: 40,
y: 40,
w: 200,
h: 70,
type: "html",
angle: 0,
desc: {
html: `
<div style="font-size: 20px;color: #666666">
<span>Hello World!</span>
</div>
<script>
window.alert('Hello World')
console.log('Hello World')
</script>
<div style="font-size: 30px; font-weight: bold; color: #666666">
<span>Hello World!</span>
</div>
`,
},
},
{
name: "html-001",
x: 200,
y: 120,
w: 150,
h: 100,
type: "html",
angle: 0,
desc: {
html: `
<style>
.btn-box {
margin: 10px 0;
}
.btn {
line-height: 1.5715;
position: relative;
display: inline-block;
font-weight: 400;
white-space: nowrap;
text-align: center;
background-image: none;
border: 1px solid transparent;
box-shadow: 0 2px #00000004;
cursor: pointer;
user-select: none;
height: 32px;
padding: 4px 15px;
font-size: 14px;
border-radius: 2px;
color: #000000d9;
background: #fff;
border-color: #d9d9d9;
}
.btn-primary {
color: #fff;
background: #1890ff;
border-color: #1890ff;
text-shadow: 0 -1px 0 rgb(0 0 0 / 12%);
box-shadow: 0 2px #0000000b;
}
</style>
<div>
<div class="btn-box">
<button class="btn">
<span>Button</span>
</button>
</div>
<div class="btn-box">
<button class="btn btn-primary">
<span>Button Primary</span>
</button>
</div>
</div>
`,
},
},
],
};
export default data;

View file

@ -0,0 +1,38 @@
<html>
<head>
<style></style>
<meta name="viewport" content="width=device-width,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<style>
html,body { margin: 0; padding: 0; }
#mount canvas {
border-right: 1px solid #aaaaaa40;
border-bottom: 1px solid #aaaaaa40;
background-image:
linear-gradient(#aaaaaa40 1px, transparent 0),
linear-gradient(90deg, #aaaaaa40 1px, transparent 0),
linear-gradient(#aaa 1px, transparent 0),
linear-gradient(90deg, #aaa 1px, transparent 0);
background-size: 10px 10px, 10px 10px, 50px 50px, 50px 50px;
}
</style>
</head>
<body>
<div id="mount"></div>
<script src="./../../dist/index.global.js"></script>
<script type="module">
import data from './../features/lib/data/html.js';
const { Core } = window.iDraw;
const core = new Core(
document.querySelector('#mount'), {
width: 600,
height: 400,
contextWidth: 600,
contextHeight: 400,
devicePixelRatio: 4
});
core.setData(data);
</script>
</body>
</html>

View file

@ -5,6 +5,7 @@ const elementTypes = {
'image': {}, // TODO
'svg': {}, // TODO
'circle': {}, // TODO
'html': {}, // TODO
};
export const elementNames = Object.keys(elementTypes);

View file

@ -0,0 +1,20 @@
import {
TypeContext,
TypeElement,
} from '@idraw/types';
import { rotateElement } from '../transform';
import Loader from '../loader';
export function drawHTML(
ctx: TypeContext,
elem: TypeElement<'html'>,
loader: Loader,
) {
const content = loader.getContent(elem.uuid);
rotateElement(ctx, elem, () => {
if (content) {
ctx.drawImage(content, elem.x, elem.y, elem.w, elem.h);
}
});
}

View file

@ -1,16 +1,14 @@
import {
TypeContext,
TypeElement,
TypeElemDesc,
// TypePoint,
} from '@idraw/types';
import { rotateElement } from '../transform';
import Loader from '../loader';
export function drawImage<T extends keyof TypeElemDesc>(
export function drawImage (
ctx: TypeContext,
elem: TypeElement<T>,
elem: TypeElement<'image'>,
loader: Loader,
) {
// const desc = elem.desc as TypeElemDesc['rect'];

View file

@ -11,6 +11,7 @@ import { clearContext, drawBgColor } from './base';
import { drawRect } from './rect';
import { drawImage } from './image';
import { drawSVG } from './svg';
import { drawHTML } from './html';
import { drawText } from './text';
import { drawCircle } from './circle';
import {
@ -55,6 +56,10 @@ export function drawContext(
drawSVG(ctx, elem as TypeElement<'svg'>, loader);
break;
}
case 'html': {
drawHTML(ctx, elem as TypeElement<'html'>, loader);
break;
}
case 'circle': {
drawCircle(ctx, elem as TypeElement<'circle'>);
break;

View file

@ -1,15 +1,14 @@
import {
TypeContext,
TypeElement,
TypeElemDesc,
} from '@idraw/types';
import { rotateElement } from '../transform';
import Loader from '../loader';
export function drawSVG<T extends keyof TypeElemDesc>(
export function drawSVG (
ctx: TypeContext,
elem: TypeElement<T>,
elem: TypeElement<'svg'>,
loader: Loader,
) {
// const desc = elem.desc as TypeElemDesc['rect'];

View file

@ -2,7 +2,7 @@
export type TypeLoadData = {
[uuid: string]: {
type: 'image' | 'svg',
type: 'image' | 'svg' | 'html',
status: 'null' | 'loaded' | 'fail',
content: null | HTMLImageElement | HTMLCanvasElement,
elemW: number;

View file

@ -2,8 +2,9 @@ import { TypeData, TypeElement, TypeElemDesc } from '@idraw/types';
import Board from '@idraw/board';
import util from '@idraw/util';
import { LoaderEvent, TypeLoadData, TypeLoaderEventArgMap } from './loader-event';
import { filterScript } from './../util/filter';
const { loadImage, loadSVG } = util.loader;
const { loadImage, loadSVG, loadHTML } = util.loader;
type Options = {
board: Board;
@ -113,7 +114,7 @@ export default class Loader {
// add new load-data
for (let i = data.elements.length - 1; i >= 0; i --) {
const elem = data.elements[i];
if (['image', 'svg',].includes(elem.type)) {
if (['image', 'svg', 'html', ].includes(elem.type)) {
if (!storageLoadData[elem.uuid]) {
loadData[elem.uuid] = this._createEmptyLoadItem(elem);
uuidQueue.push(elem.uuid);
@ -130,6 +131,12 @@ export default class Loader {
loadData[elem.uuid] = this._createEmptyLoadItem(elem);
uuidQueue.push(elem.uuid);
}
} else if (elem.type === 'html') {
const _ele = elem as TypeElement<'html'>;
if (filterScript(_ele.desc.html) !== storageLoadData[elem.uuid].source) {
loadData[elem.uuid] = this._createEmptyLoadItem(elem);
uuidQueue.push(elem.uuid);
}
}
}
}
@ -148,6 +155,7 @@ export default class Loader {
private _createEmptyLoadItem(elem: TypeElement<keyof TypeElemDesc>): TypeLoadData[string] {
let source = '';
const type: TypeLoadData[string]['type'] = elem.type as TypeLoadData[string]['type'];
if (elem.type === 'image') {
const _elem = elem as TypeElement<'image'>;
@ -155,6 +163,9 @@ export default class Loader {
} else if (elem.type === 'svg') {
const _elem = elem as TypeElement<'svg'>;
source = _elem.desc.svg || '';
} else if (elem.type === 'html') {
const _elem = elem as TypeElement<'html'>;
source = filterScript(_elem.desc.html || '');
}
return {
type: type,
@ -278,6 +289,11 @@ export default class Loader {
return image;
} else if (params && params.type === 'svg') {
const image = await loadSVG(
params.source
);
return image;
} else if (params && params.type === 'html') {
const image = await loadHTML(
params.source, {
width: params.elemW, height: params.elemH
}

View file

@ -0,0 +1,4 @@
export function filterScript(html: string) {
return html.replace(/<script[\s\S]*?<\/script>/ig, '');
}

View file

@ -28,6 +28,7 @@ type TypeElemDesc = {
circle: TypeElemDescCircle,
image: TypeElemDescImage,
svg: TypeElemDescSVG,
html: TypeElemDescHTML,
// paint: TypeElemDescPaint,
}
@ -58,6 +59,10 @@ type TypeElemDescSVG = {
svg: string;
}
type TypeElemDescHTML = {
html: string;
}
// type TypeElemDescPaint = TypePaintData
export {

View file

@ -4,7 +4,7 @@ import { toColorHexStr, toColorHexNum, isColorStr } from './lib/color';
import { createUUID } from './lib/uuid';
import { deepClone } from './lib/data';
import istype from './lib/istype';
import { loadImage, loadSVG } from './lib/loader';
import { loadImage, loadSVG, loadHTML } from './lib/loader';
export default {
time: {
@ -15,6 +15,7 @@ export default {
loader: {
loadImage,
loadSVG,
loadHTML,
},
file: {
downloadImageFromCanvas,

View file

@ -13,25 +13,39 @@ export function loadImage(src: string): Promise<HTMLImageElement> {
}
export function loadSVG(svg: string, opts?: { width: number, height: number }): Promise<HTMLImageElement> {
export function loadSVG(svg: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
// let style = '';
// if (opts) {
// style = `min-height: ${opts.height}px; min-width: ${opts.width}px`
// }
// const _svg = `
// <svg xmlns="http://www.w3.org/2000/svg"
// width="${opts?.width || ''}"
// height="${opts?.height || ''}"
// style="${style}" >
// <foreignObject width="100%" height="100%">
// ${svg}
// </foreignObject>
// </svg>
// `;
const _svg = svg;
const image = new Image();
const blob = new Blob([_svg], { type: 'image/svg+xml;charset=utf-8'});
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onload = function (event: ProgressEvent<FileReader>) {
const base64: string = event?.target?.result as string;
image.onload = function() {
resolve(image);
};
image.src = base64;
};
reader.onerror = function(err) {
reject(err);
};
});
}
export function loadHTML(html: string, opts: { width: number, height: number }): Promise<HTMLImageElement> {
const { width, height } = opts;
return new Promise((resolve, reject) => {
const _svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${width || ''}" height = "${height || ''}">
<foreignObject width="100%" height="100%">
<div xmlns = "http://www.w3.org/1999/xhtml">
${html}
</div>
</foreignObject>
</svg>
`;;
const image = new Image();
const blob = new Blob([_svg], { type: 'image/svg+xml;charset=utf-8'});
const reader = new FileReader();