feat: improve scroller of iDraw.js board

This commit is contained in:
chenshenhai 2023-03-18 22:04:44 +08:00
parent 9a1aacb924
commit 5a5b4708e1
7 changed files with 200 additions and 122 deletions

View file

@ -1,91 +1,105 @@
import {
TypePoint,
TypeScreenPosition,
TypeBoardScrollConfig,
TypeBoardScrollConfig
} from '@idraw/types';
import { isColorStr } from '@idraw/util';
type TypeOptions = {
width: number,
height: number,
devicePixelRatio: number,
scrollConfig?: TypeBoardScrollConfig,
width: number;
height: number;
devicePixelRatio: number;
scrollConfig?: TypeBoardScrollConfig;
};
type TypePrivateOptions = TypeOptions & {
width: number,
height: number,
devicePixelRatio: number,
scrollConfig: TypeBoardScrollConfig,
}
type TypePrivateOptions = Required<
TypeOptions & { scrollConfig: Required<TypeBoardScrollConfig> }
>;
const defaultScrollConfig = {
lineWidth: 12,
color: '#a0a0a0'
const minScrollerWidth = 12;
const scrollerAlpha = 0.12;
const scrollerThumbAlpha = 0.36;
const defaultScrollConfig: Partial<TypeBoardScrollConfig> & {
width: number;
color: string;
} = {
width: minScrollerWidth,
color: '#000000',
showBackground: true
};
export class Scroller {
private _displayCtx: CanvasRenderingContext2D;
private _opts: TypePrivateOptions;
constructor(
ctx: CanvasRenderingContext2D,
opts: TypeOptions
) {
constructor(ctx: CanvasRenderingContext2D, opts: TypeOptions) {
this._displayCtx = ctx;
this._opts = this._getOpts(opts);
}
draw(position: TypeScreenPosition) {
const { width, height } = this._opts;
const { width, height, scrollConfig } = this._opts;
const wrapper = this.calc(position);
const ctx = this._displayCtx;
if (wrapper.xSize > 0) {
ctx.globalAlpha = 0.2;
ctx.fillStyle = wrapper.color;
// x-line
ctx.fillRect(0, this._doSize(height - wrapper.lineSize), this._doSize(width), this._doSize(wrapper.lineSize));
ctx.globalAlpha = 1;
if (wrapper.xSize > 0) {
if (scrollConfig.showBackground === true) {
ctx.globalAlpha = scrollerAlpha;
ctx.fillStyle = wrapper.color;
// x-line
ctx.fillRect(
0,
this._doSize(height - wrapper.lineSize),
this._doSize(width),
this._doSize(wrapper.lineSize)
);
}
// ctx.globalAlpha = 1;
// x-slider
drawBox(ctx, {
drawBoxScrollerThumb(ctx, {
axis: 'X',
x: this._doSize(wrapper.translateX),
y: this._doSize(height - wrapper.lineSize),
w: this._doSize(wrapper.xSize),
h: this._doSize(wrapper.lineSize),
r: this._doSize(wrapper.lineSize / 2),
color: wrapper.color,
color: wrapper.color
});
}
if (wrapper.ySize > 0) {
ctx.globalAlpha = 0.2;
ctx.fillStyle = wrapper.color;
if (scrollConfig.showBackground === true) {
ctx.globalAlpha = scrollerAlpha;
ctx.fillStyle = wrapper.color;
// y-line
ctx.fillRect(
this._doSize(width - wrapper.lineSize),
0,
this._doSize(wrapper.lineSize),
this._doSize(height)
);
}
// y-line
ctx.fillRect(this._doSize(width - wrapper.lineSize), 0, this._doSize(wrapper.lineSize), this._doSize(height));
ctx.globalAlpha = 1;
// ctx.globalAlpha = 1;
// y-slider
drawBox(ctx, {
drawBoxScrollerThumb(ctx, {
axis: 'Y',
x: this._doSize(width - wrapper.lineSize),
y: this._doSize(wrapper.translateY),
w: this._doSize(wrapper.lineSize),
h: this._doSize(wrapper.ySize),
r: this._doSize(wrapper.lineSize / 2),
color: wrapper.color,
color: wrapper.color
});
}
ctx.globalAlpha = 1;
}
resetSize(opts: { width: number, height: number, devicePixelRatio: number }) {
this._opts = {...this._opts, ...opts};
resetSize(opts: { width: number; height: number; devicePixelRatio: number }) {
this._opts = { ...this._opts, ...opts };
}
isPointAtScrollY(p: TypePoint): boolean {
@ -93,15 +107,15 @@ export class Scroller {
const ctx = this._displayCtx;
ctx.beginPath();
ctx.rect(
this._doSize(width - scrollConfig.lineWidth),
0,
this._doSize(scrollConfig.lineWidth),
this._doSize(width - scrollConfig.width),
0,
this._doSize(scrollConfig.width),
this._doSize(height)
);
ctx.closePath();
if (ctx.isPointInPath(this._doSize(p.x), this._doSize(p.y))) {
return true;
}
}
return false;
}
@ -110,42 +124,50 @@ export class Scroller {
const ctx = this._displayCtx;
ctx.beginPath();
ctx.rect(
0,
this._doSize(height - scrollConfig.lineWidth),
this._doSize(width - scrollConfig.lineWidth),
this._doSize(scrollConfig.lineWidth)
0,
this._doSize(height - scrollConfig.width),
this._doSize(width - scrollConfig.width),
this._doSize(scrollConfig.width)
);
ctx.closePath();
if (ctx.isPointInPath(this._doSize(p.x), this._doSize(p.y))) {
return true;
}
}
return false;
}
getLineWidth(): number {
const lineWidth = this._opts.scrollConfig.lineWidth;
const lineWidth = this._opts.scrollConfig.width;
return lineWidth;
}
calc(position: TypeScreenPosition) {
const { width, height, scrollConfig } = this._opts;
const sliderMinSize = scrollConfig.lineWidth * 2.5;
const lineSize = scrollConfig.lineWidth;
const sliderMinSize = scrollConfig.width * 2.5;
const lineSize = scrollConfig.width;
let xSize = 0;
let ySize = 0;
if (position.left <= 0 && position.right <= 0) {
xSize = Math.max(sliderMinSize, width - (Math.abs(position.left) + Math.abs(position.right)));
xSize = Math.max(
sliderMinSize,
width - (Math.abs(position.left) + Math.abs(position.right))
);
if (xSize >= width) xSize = 0;
}
if (position.top <= 0 || position.bottom <= 0) {
ySize = Math.max(sliderMinSize, height - (Math.abs(position.top) + Math.abs(position.bottom)));
ySize = Math.max(
sliderMinSize,
height - (Math.abs(position.top) + Math.abs(position.bottom))
);
if (ySize >= height) ySize = 0;
}
let translateX = 0;
if (xSize > 0) {
translateX = xSize / 2 + (width - xSize) * Math.abs(position.left) / (Math.abs(position.left) + Math.abs(position.right));
translateX =
xSize / 2 +
((width - xSize) * Math.abs(position.left)) /
(Math.abs(position.left) + Math.abs(position.right));
translateX = Math.min(Math.max(0, translateX - xSize / 2), width - xSize);
// const xUnit = this.calcScreenScrollUnit(position.left, position.right, xSize, width);
// translateX = translateX * xUnit;
@ -153,8 +175,14 @@ export class Scroller {
let translateY = 0;
if (ySize > 0) {
translateY = ySize / 2 + (height - ySize) * Math.abs(position.top) / (Math.abs(position.top) + Math.abs(position.bottom));
translateY = Math.min(Math.max(0, translateY - ySize / 2), height - ySize);
translateY =
ySize / 2 +
((height - ySize) * Math.abs(position.top)) /
(Math.abs(position.top) + Math.abs(position.bottom));
translateY = Math.min(
Math.max(0, translateY - ySize / 2),
height - ySize
);
// const yUnit = this.calcScreenScrollUnit(position.top, position.bottom, ySize, height);
// translateY = translateY * yUnit;
}
@ -173,16 +201,25 @@ export class Scroller {
return num * this._opts.devicePixelRatio;
}
private _getOpts(opts: TypeOptions): TypePrivateOptions {
const options = { ...{ scrollConfig: defaultScrollConfig }, ...opts};
console.log('opts ====', opts);
const options: TypePrivateOptions = {
...opts,
...{
scrollConfig: { ...defaultScrollConfig, ...(opts.scrollConfig || {}) }
}
} as TypePrivateOptions;
if (!options.scrollConfig) {
options.scrollConfig = defaultScrollConfig;
options.scrollConfig =
defaultScrollConfig as TypePrivateOptions['scrollConfig'];
}
if (!(options.scrollConfig.lineWidth > 0)) {
options.scrollConfig.lineWidth = defaultScrollConfig.lineWidth;
if (!(options?.scrollConfig?.width > 0)) {
options.scrollConfig.width = defaultScrollConfig.width;
}
options.scrollConfig.lineWidth = Math.max(options.scrollConfig.lineWidth, defaultScrollConfig.lineWidth);
options.scrollConfig.width = Math.max(
options.scrollConfig.width,
defaultScrollConfig.width
);
if (isColorStr(options.scrollConfig.color) !== true) {
options.scrollConfig.color = options.scrollConfig.color;
@ -191,25 +228,53 @@ export class Scroller {
}
}
function drawBox(
function drawBoxScrollerThumb(
ctx: CanvasRenderingContext2D,
opts: { x: number, y: number, w: number, h: number, r: number, color: string }
opts: {
axis: 'X' | 'Y';
x: number;
y: number;
w: number;
h: number;
r: number;
color: string;
}
): void {
let { x, y, h, w } = opts;
const { color, axis } = opts;
if (axis === 'X') {
y = y + h / 4 + 1;
h = h / 2;
} else if (axis === 'Y') {
x = x + w / 4 + 1;
w = w / 2;
}
const { x, y, w, h, color } = opts;
let r = opts.r;
r = Math.min(r, w / 2, h / 2);
if (w < r * 2 || h < r * 2) {
r = 0;
}
ctx.globalAlpha = scrollerThumbAlpha;
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
}
ctx.fill();
ctx.globalAlpha = 1;
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = color;
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.stroke();
}

View file

@ -93,8 +93,9 @@ export default class Core {
...this[_opts],
canScroll: config?.scrollWrapper?.use,
scrollConfig: {
color: config?.scrollWrapper?.color || '#a0a0a0',
lineWidth: config?.scrollWrapper?.lineWidth || 12
color: config?.scrollWrapper?.color || '#000000',
width: config?.scrollWrapper?.width || 12,
...(config?.scrollWrapper || {})
}
});
this[_renderer] = new Renderer();
@ -173,8 +174,8 @@ export default class Core {
return selectElement(this, uuid);
}
cancelElementByIndex(index: number, opts?: { useMode?: boolean }): void {
return cancelElementByIndex(this, index, opts);
cancelElementByIndex(index: number): void {
return cancelElementByIndex(this, index);
}
cancelElement(uuid: string, opts?: { useMode?: boolean }): void {

View file

@ -27,17 +27,21 @@ const idraw = new iDraw(
{
scrollWrapper: {
use: true
// color: 'red'
// showBackground: false
}
}
);
idraw.setData(data);
const parseData = idraw.getData();
// const parseData = idraw.getData();
idraw.on('changeData', (d) => {
console.log('changeData ======', d);
});
idraw.scale(1.5);
idraw.selectElementByIndex(1);
setTimeout(() => {

View file

@ -8,5 +8,5 @@ export const defaultOptions: PrivateOptions = {
devicePixelRatio: 1,
onlyRender: false,
maxRecords: 10,
disableKeyboard: true,
}
disableKeyboard: true
};

View file

@ -1,7 +1,7 @@
import {
TypeContext,
// TypeElemDesc,
TypeElement,
TypeElement
} from '@idraw/types';
import { is, istype, isColorStr } from '@idraw/util';
import { rotateElement } from './../transform';
@ -14,7 +14,7 @@ export function clearContext(ctx: TypeContext) {
ctx.setLineDash([]);
ctx.setGlobalAlpha(1);
ctx.setShadowColor('#00000000');
ctx.setShadowOffsetX(0)
ctx.setShadowOffsetX(0);
ctx.setShadowOffsetY(0);
ctx.setShadowBlur(0);
}
@ -28,7 +28,7 @@ export function drawBgColor(ctx: TypeContext, color: string) {
export function drawBox(
ctx: TypeContext,
elem: TypeElement<'text' | 'rect'>,
pattern: string | CanvasPattern | null,
pattern: string | CanvasPattern | null
): void {
clearContext(ctx);
drawBoxBorder(ctx, elem);
@ -46,20 +46,19 @@ export function drawBox(
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.closePath();
if (typeof pattern === 'string') {
ctx.setFillStyle(pattern);
ctx.setFillStyle(pattern);
} else if (['CanvasPattern'].includes(istype.type(pattern))) {
ctx.setFillStyle(pattern as CanvasPattern);
}
ctx.fill();
ctx.fill();
});
}
export function drawBoxBorder(
ctx: TypeContext,
elem: TypeElement<'text'|'rect'>,
elem: TypeElement<'text' | 'rect'>
): void {
clearContext(ctx);
rotateElement(ctx, elem, () => {
@ -102,7 +101,7 @@ export function drawBoxBorder(
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
ctx.stroke();
ctx.closePath();
ctx.stroke();
});
}
}

View file

@ -1,12 +1,13 @@
type TypePoint = {
x: number;
y: number;
}
};
type TypeBoardScrollConfig = {
color: string,
lineWidth: number
}
color: string;
width: number;
showBackground?: boolean;
};
type TypeBoardSizeOptions = {
width?: number;
@ -14,7 +15,7 @@ type TypeBoardSizeOptions = {
contextWidth?: number;
contextHeight?: number;
devicePixelRatio?: number;
}
};
type TypeBoardOptions = TypeBoardSizeOptions & {
width: number;
@ -22,16 +23,26 @@ type TypeBoardOptions = TypeBoardSizeOptions & {
contextWidth: number;
contextHeight: number;
canScroll?: boolean;
scrollConfig?: TypeBoardScrollConfig
}
scrollConfig?: TypeBoardScrollConfig;
};
type TypePointCursor = 'auto' | 'move' | 'n-resize' | 'e-resize' | 's-resize' | 'w-resize'
| 'ne-resize' | 'nw-resize' | 'se-resize' | 'sw-resize' | 'grab';
type TypePointCursor =
| 'auto'
| 'move'
| 'n-resize'
| 'e-resize'
| 's-resize'
| 'w-resize'
| 'ne-resize'
| 'nw-resize'
| 'se-resize'
| 'sw-resize'
| 'grab';
export {
TypePoint,
TypePointCursor,
TypeBoardSizeOptions,
TypeBoardOptions,
TypeBoardScrollConfig,
};
TypeBoardScrollConfig
};

View file

@ -1,28 +1,26 @@
type TypeConfig = {
elementWrapper?: {
color?: string,
controllerSize?: number,
lineWidth?: number,
lineDash?: number[],
},
color?: string;
controllerSize?: number;
lineWidth?: number;
lineDash?: number[];
};
scrollWrapper?: {
use?: boolean,
color?: string,
lineWidth?: number,
}
}
use?: boolean;
color?: string;
width?: number;
showBackground?: boolean;
};
};
type TypeConfigStrict = TypeConfig & {
elementWrapper: {
color: string,
lockColor: string,
controllerSize: number,
lineWidth: number,
lineDash: number[],
},
}
color: string;
lockColor: string;
controllerSize: number;
lineWidth: number;
lineDash: number[];
};
};
export {
TypeConfig,
TypeConfigStrict
};
export { TypeConfig, TypeConfigStrict };