svg는 그래픽 편집기 시리즈 9를 구현합니다: 스프라이트의 편집 상태 및 공통 스프라이트 개발

스프라이트 편집 상태 및 렌더링 상태

이제 캔버스의 다양한 편집 기능은 비교적 풍부하지만 모두 일반적인 편집 기능이므로 일부 스프라이트 자체가 특별한 편집 기능을 원하면 당분간 그렇게 할 수 없습니다. 예를 들어:

  • 서식 있는 텍스트 마법사가 텍스트 서식을 편집하기 위한 도구 모음을 표시하려고 합니다.
  • 그림 스프라이트는 그림의 링크 주소를 구성하기 위한 입력 상자를 표시하려고 합니다.
  • 일부 스프라이트 구성 요소는 먼저 간단한 구성 형식 등을 표시합니다(복잡하고 많은 구성 항목은 오른쪽에서 속성 구성 패널을 팝업하여 구현하는 것이 좋습니다. 이 논의 범위를 벗어납니다).

따라서 우리는 스프라이트가 존재 渲染模式하고 编辑模式기본적으로 스프라이트가 렌더링 상태에 있으며 스프라이트를 편집 双击精灵상태 정의합니다.

편집 상태인지 여부에 관계없이 스프라이트 구성 요소에 매개 editing변수를 하고 스프라이트 자체는 각각 렌더링 상태와 편집 상태에 표시되는 콘텐츠를 제어합니다.

  • 에디터 코어는 더블클릭 이벤트 리스너를 바인딩하고 편집 중인 스프라이트를 설정합니다. 스프라이트 렌더링 시 스프라이트 편집 여부를 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 Tutorial - Novice Tutorial

  • svg 그리기에는 다양한 공통 방법이 있습니다.이미지.png

  • 온라인에서 미리 볼 수 있는 svg의 예

이미지.png

일반적인:

  • 단어
  • 그림
  • 링크
  • 오디오
  • 동영상
  • 시트

모양:

  • 직사각형
  • 둥근 사각형
  • 가리키다
  • 삼각형
  • 타원형
  • 평행사변형
  • 다이아몬드
  • 오각형
  • 육각형
  • 부문
  • 자유 다각형
  • 입방체
  • 실린더

라인 클래스:

  • 선분
  • 광선
  • 일직선
  • 파선
  • 2차 베지어 곡선
  • 3차 베지어 곡선
  • 부드러운 곡선(꺾은선형 차트의 점을 부드러운 곡선으로 연결)
  • 자유 경로 곡선(마우스로 그린 곡선)

연결 라인 유형:

  • 케이블
  • 직각 커넥터
  • 부드러운 곡선 커넥터

다른:

  • 회전목마
  • iframe 페이지
  • 타이머, 카운트다운
  • 시계
  • 수식(LaTex 기반)
  • 코드 하이라이트 블록
  • 다른...

Supongo que te gusta

Origin juejin.im/post/7215620087825481784
Recomendado
Clasificación