svg はグラフィック エディター シリーズ 9 を実装します: スプライトの編集状態と一般的なスプライトの開発

スプライトの編集状態とレンダリング状態

現在、キャンバスのさまざまな編集機能は比較的豊富ですが、それらはすべて一般的な編集機能であり、一部のスプライト自体が特別な編集機能を必要とする場合、当面はそれを行うことができません。例えば:

  • リッチ テキスト ウィザードは、テキストの書式設定を編集するためのツールバーを表示したい
  • ピクチャ スプライトは、ピクチャのリンク アドレスを設定するための入力ボックスを表示したい
  • 一部のスプライト コンポーネントは最初に単純な構成フォームなどを表示します (複雑で多くの構成項目は、右側からプロパティ構成パネルをポップアップ表示することで実装することをお勧めしますが、これはこの説明の範囲外です)

そこで、スプライトが存在することを定義し渲染模式编辑模式デフォルトでは、スプライトはレンダリング状態にあり、その双击精灵時点。

編集状態かどうかにかかわらず、スプライト コンポーネントにeditingパラメーター、スプライト自体がレンダリング状態と編集状態でそれぞれどのコンテンツを表示するかを制御します。

  • The editor core binds the double-click event listener, and sets the sprite being edit. スプライトをレンダリングするとき、スプライトが編集されているかどうかが 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>
    );
  }
}
复制代码
  • テキスト スプライトを例として使用して、スプライトの編集を説明する

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>
    );
  }
}
复制代码

いくつかの一般的に使用されるスプライト

1.リッチテキスト

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. 写真


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. リンク


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;
复制代码

その他の一般的なグラフィック スプライトの提案

これに基づいて多くの有用なスプライトを開発し、コメント領域で共有するようにみんなに勧めてください〜

画像.png

SVG 描画の使用に慣れていない学生は、ここから学習できます: SVG チュートリアル - 初心者向けチュートリアル

  • svg 描画にはさまざまな一般的な方法があります画像.png

  • オンラインでプレビューできる svg の例

画像.png

全般的:

  • 言葉
  • 写真
  • リンク
  • オーディオ
  • ビデオ
  • シート

形:

  • 矩形
  • 角丸長方形
  • 三角形
  • 楕円形
  • 平行四辺形
  • ダイヤモンド
  • 五角形
  • 六角形
  • セクタ
  • フリーポリゴン
  • 立方体
  • シリンダー

回線クラス:

  • 線分
  • レイズ
  • 直線
  • 破線
  • 二次ベジエ曲線
  • 三次ベジエ曲線
  • Smooth Curve (折れ線グラフのポイントを滑らかな曲線で結びます)
  • 自由曲線(マウスで描いた曲線)

接続線の種類:

  • ケーブル
  • 直角コネクタ
  • 滑らかな曲線コネクタ

他の:

  • カルーセル
  • iframe ページ
  • タイマー、カウントダウン
  • 時計
  • 数式 (LaTex に基づく)
  • コード ハイライト ブロック
  • 他の...

おすすめ

転載: juejin.im/post/7215620087825481784