mirror of
https://github.com/idrawjs/idraw
synced 2026-05-24 10:08:34 +00:00
feat: make @idraw/core support html element
This commit is contained in:
parent
22a8a360f1
commit
796833d138
13 changed files with 217 additions and 27 deletions
89
packages/core/examples/features/lib/data/html.js
Normal file
89
packages/core/examples/features/lib/data/html.js
Normal 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;
|
||||
38
packages/core/examples/test/html.html
Normal file
38
packages/core/examples/test/html.html
Normal 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>
|
||||
|
|
@ -5,6 +5,7 @@ const elementTypes = {
|
|||
'image': {}, // TODO
|
||||
'svg': {}, // TODO
|
||||
'circle': {}, // TODO
|
||||
'html': {}, // TODO
|
||||
};
|
||||
|
||||
export const elementNames = Object.keys(elementTypes);
|
||||
|
|
|
|||
20
packages/core/src/lib/draw/html.ts
Normal file
20
packages/core/src/lib/draw/html.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -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'];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'];
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
4
packages/core/src/util/filter.ts
Normal file
4
packages/core/src/util/filter.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export function filterScript(html: string) {
|
||||
return html.replace(/<script[\s\S]*?<\/script>/ig, '');
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue