svg implements graphic editor series nine: the editing state of sprites and the development of common sprites

Sprite editing state and rendering state

Now the canvas has various editing capabilities, but they are all general-purpose editing capabilities. If some sprites want some special editing functions, they cannot do so for the time being. For example:

  • The rich text wizard wants to display a toolbar for editing text formatting
  • The picture sprite wants to display the input box for configuring the link address of the picture
  • Some sprite components first display a simple configuration form and so on (complex and many configuration items are suggested to be implemented by popping up a property configuration panel from the right, which is out of the scope of this discussion)

So we define that the sprite exists 渲染模式and 编辑模式, by default, the sprite is in the rendering state, and will make the sprite into the editing state at that 双击精灵time .

Whether it is in the editing state or not, we pass a editingparameter to the sprite component, and the sprite itself controls what content is displayed in the rendering state and editing state, respectively.

  • The editor core binds the double-click event listener, and sets the sprite being edited. When rendering the sprite, whether the sprite is being edited is passed in as props
export class GraphicEditorCore extends React.Component<IProps, IState> {
  state = {
    editingSprite,
    // 其他属性...
  };

  componentDidMount() {
    document.addEventListener('dblclick', this.handleDoubleClick);
  }

  handleDoubleClick = (e: MouseEvent) => {
    if (!isClickOnSprite(e)) {
      this.setState({ editingSprite: null });
      return;
    }
    const editingSprite = getSpriteByEvent(e);
    this.setState({ editingSprite });
  };

  // 递归渲染精灵的方法(这里只放表达将编辑态传入精灵的伪代码)
  renderSprite = (sprite: ISprite) => {
    const { editingSprite } = this.state;
    const editing = editingSprite?.id && editingSprite?.id === sprite.id;
    return (
      <Sprite key={sprite.id} sprite={sprite}>
        <SpriteComponent sprite={sprite} editing={editing} />
      </Sprite>
    );
  }
}
复制代码
  • Using text sprites as an example to illustrate editing state sprites

const TextEditor = ({ content, onChange }) => {
  return (
    // 使用foreignObject包裹,即可实现在svg内显示普通html标签
    <foreignObject x={0} y={0} width={width} height={height}>
      <input
        value={content}
        onChange={(e) => onChange(e.target.value)}
      />
    </foreignObject>
  );
};

export class TextSprite extends BaseSprite<IProps> {
  render() {
    const { sprite, editing } = this.props;
    const { content } = sprite.props;
    const { updateSpriteProps } = this.props.stage.apis;
    // 编辑态
    if (editing) {
      return (
        <TextEditor
          content={content}
          onChange={value => updateSpriteProps(sprite, { content: value })}
        />
      );
    }
    // 渲染态
    return (
      <text>{content}</text>
    );
  }
}
复制代码

some commonly used sprites

1. Rich text

import React from 'react';
import type { ISpriteMeta } from '../../interface';
import { BaseSprite } from '../BaseSprite';

const RichTextEditor = ({ content, onChange }) => {
  return (
    // 使用foreignObject包裹,即可实现在svg内显示普通html标签
    <foreignObject
      {...props}
      x={0}
      y={0}
      width={width}
      height={height}
      style={{
        width: `${width}px`,
        height: `${height}px`,
        border: '1px solid #aaa',
        padding: '3px 5px',
        userSelect: 'none',
      }}>
      <div
        style={{ height: '100%', outline: 'none' }}
        contentEditable={editing}
        dangerouslySetInnerHTML={{ __html: props.content }}></div>
    </foreignObject>
  );
};


interface IProps {
  width: number;
  height: number;
  content: string;
}

const SpriteType = 'TextSprite';

export class TextSprite extends BaseSprite<IProps> {

  handleChange = (prop: string, value: string) => {
    const { sprite, stage } = this.props;
    stage.apis.updateSpriteProps(sprite, { [prop]: value });
  };

  render() {
    const { sprite, editing } = this.props;
    const { props, attrs } = sprite;
    const { width, height } = attrs.size;
    const { updateSpriteProps } = this.props.stage.apis;
    return (
      <RichTextEditor
        content={content}
        onChange={value => updateSpriteProps(sprite, { content: value })}
      />
    );
  }
}

export const TextSpriteMeta: ISpriteMeta<IProps> = {
  type: SpriteType,
  spriteComponent: TextSprite,
  initProps: {
    width: 100,
    height: 40,
    content: '',
  },
};

export default TextSpriteMeta;


复制代码

2. Pictures


import React from 'react';
import { BaseSprite } from '../BaseSprite';
import type { ISpriteMeta } from '../../interface';
import './index.less';

const ImageSrcInput = ({ content, onChange }) => {
  return (
    <foreignObject
      width={300}
      height={30}
      x={0}
      y={0}
      style={{ padding: '0 4px', overflow: 'visible' }}>
      <label>src</label><input
        value={url}
        onChange={(e) => onChange(e.target.value)}
      />
    </foreignObject>
  );
};

interface IProps {
  url: string;
}

const SpriteType = 'ImageSprite';

export class ImageSprite extends BaseSprite<IProps> {
  render() {
    const { sprite, editing } = this.props;
    const { props, attrs } = sprite;
    const { width, height } = attrs.size;
    const { url } = props;
    const { updateSpriteProps } = this.props.stage.apis;

    return (
      <g className="image-sprite-content">
        <image xlinkHref={url} x={0} y={0} width={width} height={height} />
        // 编辑态
        {editing && (
          <TextEditor
            content={content}
            onChange={value => updateSpriteProps(sprite, { url: value })}
          />
        )}
      </g>
    );
  }
}

export const ImageSpriteMeta: ISpriteMeta<IProps> = {
  type: SpriteType,
  spriteComponent: ImageSprite,
  operation: {
    resizeLock: true,
  },
  initProps: {
    url: '/img',
  },
};

export default ImageSpriteMeta;

复制代码

3. Links


import React from 'react';
import type { IDefaultGraphicProps, ISpriteMeta } from '../../interface';
import { BaseSprite } from '../BaseSprite';
import './index.less';

const LinkEditor = ({ href, target, text, onChange }) => {
  return (
    <foreignObject
      width={width}
      height={height}
      x={0}
      y={0}
      style={{ padding: '0 4px', overflow: 'visible' }}>
      <a
        className="link-sprite-content"
        href={href}
        target={target}
        style={{ color: '#1890ff' }}>
        {text}
      </a>
      <div
        className="link-sprite-panel"
        style={{ top: `${height + 5}px` }}
        onMouseDown={(e: any) => e.stopPropagation()}>
        <div className="link-sprite-row-item">
          <label className="link-sprite-row-label">文字</label><input
            className="link-sprite-row-input"
            value={text}
            onChange={(e: any) =>
              onChange('text', e.target.value)
            }
          />
        </div>
        <div className="link-sprite-row-item">
          <label className="link-sprite-row-label">链接</label><input
            className="link-sprite-row-input"
            value={href}
            onChange={(e: any) =>
              onChange('href', e.target.value)
            }
          />
        </div>
        <div className="link-sprite-row-item">
          <label className="link-sprite-row-label">新页面打开</label><input
            className="link-sprite-row-radio"
            type="radio"
            name="target"
            value={target}
            checked={target === '_blank'}
            onChange={() => onChange('target', '_blank')}
          />
          是
          <input
            className="link-sprite-row-radio"
            type="radio"
            name="target"
            style={{ marginLeft: '10px' }}
            value={target}
            checked={target === '_self'}
            onChange={() => onChange('target', '_self')}
          />
          否
          {/* <div className="button-container primary-button-container">确定</div> */}
        </div>
      </div>
    </foreignObject>
  );
}

interface IProps extends IDefaultGraphicProps {
  href: string;
  text: string;
  target?: '_blank' | '_self' | '_parent' | '_top';
}

const SpriteType = 'LinkSprite';

export class LinkSprite extends BaseSprite<IProps> {
  handleChange = (name: string, value: string) => {
    const { sprite, stage } = this.props;
    const { updateSpriteProps } = stage.apis;
    updateSpriteProps(sprite, { [name]: value });
  };

  render() {
    const { sprite, editing } = this.props;
    const { props, attrs } = sprite;
    const { size, coordinate } = attrs;
    const { width, height } = size;
    const { x, y } = coordinate;
    const { href = '', text = '', target = '_self' } = props;
    if (editing) {
      return (
        <LinkEditor
          href={href}
          target={target}
          text={text}
          onChange={this.handleChange}
        />
      );
    }
    return (
      <>
        <a xlinkHref={href} target="new" style={{ userSelect: 'none' }}>
          <text x={x + 4} y={y + 16} fill="#1890ff" dominantBaseline="end">{text}</text>
        </a>
      </>
    );
  }
}

export const LinkSpriteMeta: ISpriteMeta<IProps> = {
  type: SpriteType,
  spriteComponent: LinkSprite,
  initProps: {
    href: '',
    text: '链接',
    target: '_blank',
  },
};

export default LinkSpriteMeta;
复制代码

Other Common Graphics Sprite Suggestions

Encourage everyone to develop many useful sprites on this basis, and then share them in the comment area~

image.png

Students who are not familiar with using svg drawing can learn from here: SVG Tutorial - Novice Tutorial

  • There are various common methods for svg drawingimage.png

  • An example of svg that can be previewed online

image.png

General:

  • Word
  • picture
  • Link
  • audio
  • video
  • sheet

shape:

  • rectangle
  • Rounded Rectangle
  • point
  • triangle
  • oval
  • Parallelogram
  • diamond
  • pentagon
  • hexagon
  • sector
  • free polygon
  • cube
  • Cylinder

Line class:

  • line segment
  • Rays
  • straight line
  • broken line
  • second-order Bezier curve
  • Third-order Bezier curve
  • Smooth Curve (connect the points of the line chart with a smooth curve)
  • Free path curves (curves drawn with the mouse)

Connecting line type:

  • cable
  • right angle connector
  • smooth curve connector

other:

  • Carousel
  • iframe page
  • timer, countdown
  • clock
  • formula (based on LaTex)
  • code highlight block
  • other...

Guess you like

Origin juejin.im/post/7215620087825481784