最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 今天是儿童节,整个贪吃蛇到编辑器里玩儿吧

    正文概述 掘金(DevUI团队)   2021-06-01   405

    DevUI 是一款面向企业中后台产品的开源前端解决方案,它倡导沉浸灵活至简的设计价值观,提倡设计者为真实的需求服务,为多数人的设计,拒绝哗众取宠、取悦眼球的设计。如果你正在开发 ToB工具类产品,DevUI 将是一个很不错的选择!

    今天是儿童节,整个贪吃蛇到编辑器里玩儿吧

    引言

    今天是六一儿童节,突然想起来前段时间看过的对半同学写的贪吃蛇游戏,据说对半同学只花了一个小时就写出来了。

    《canvas 300行代码实现一个贪吃蛇 》

    当时我正躺在床上刷手机,看到对半同学的这篇文章,立马坐了起来,认真看完之后立马点了赞,并且评论了。

    今天是儿童节,整个贪吃蛇到编辑器里玩儿吧

    虽然这篇文章没有火起来,但并不影响这是一篇好文。

    于是我在想

    大家在日常工作中有遇到往编辑器里插入什么奇葩内容呢?

    欢迎在评论区讨论。

    依然是插入自定义内容

    不多啰嗦了,直接参考之前的文章:

    《Quill富文本编辑器的实践》

    按照以下四个步骤来就行:

    • 第一步:自定义工具栏按钮
    • 第二步:自定义Blot内容
    • 第三步:在Quill注册自定义Blot
    • 第四步:调用Quill的API插入自定义内容

    第一步:自定义工具栏按钮

    这个非常简单:

    const TOOLBAR_CONFIG = [
      [{ header: ['1', '2', '3', false] }],
      ['bold', 'italic', 'underline', 'link'],
      [{ list: 'ordered' }, { list: 'bullet' }],
      ['clean'],
      ['card', 'divider', 'emoji', 'file', 'tag'],
      ['dragon', 'snake'], // 新增的
    ];
    

    自定义工具栏按钮图标:

    const snakeIcon = `<svg>...</svg>`;
    const icons = Quill.import('ui/icons');
    icons.snake = snakeIcon;
    

    增加工具栏按钮事件:

    const quill = new Quill('#editor', {
      theme: 'snow',
      modules: {
        toolbar: {
          container: TOOLBAR_CONFIG,
          handlers: {
            ...
            // 增加一个空的事件
            snake(value): void {
              console.log('snake~~');
            },
          },
        }
      },
    });
    

    第二步:自定义Blot内容 SnakeBlot

    不再啰嗦,参考之前的文章直接写就好:

    《如何将龙插入到编辑器中? 》

    《Quill富文本编辑器的实践》

    snake.ts

    import Quill from 'quill';
    import GreedySnake from '../../shared/greedy-snake';
    
    const BlockEmbed = Quill.import('blots/block/embed');
    
    class SnakeBlot extends BlockEmbed {
      static blotName = 'snake';
      static tagName = 'canvas';
    
      static create(value): any {
        const node = super.create(value);
        const { id, width, height } = value;
    
        node.setAttribute('id', id || SnakeBlot.blotName);
        if (width !== undefined) {
          node.setAttribute('width', width);
        }
        if (height !== undefined) {
          node.setAttribute('height', height);
        }
    
        // 绘制贪吃蛇游戏的代码参考对半同学的文章:https://juejin.cn/post/6959789039566192654
        new GreedySnake(node).start();
        
        return node;
      }
    }
    
    export default SnakeBlot;
    
    

    绘制贪吃蛇

    由于对半同学花一个小时写出来的代码实在非常优雅,忍不住将其代码贴出来了(侵删),文章源码来源于对半同学的文章:

    canvas 300行代码实现一个贪吃蛇

    greedy-snake.ts

    // 大小为64 * 40
    export default class GreedySnake {
      canvas;
      ctx;
      maxX;
      maxY;
      itemWidth;
      direction;
      speed;
      isStop;
      isOver;
      isStart;
      score;
      timer;
      j;
      canChange;
      grid;
      snake;
      food;
    
      // mask;
      // scoreDom;
    
      constructor(container) {
        this.canvas = typeof container === 'string' ? document.querySelector(container) : container;
        this.canvas.setAttribute('width', 640);
        this.canvas.setAttribute('height', 400);
        this.canvas.setAttribute('style', 'border: solid 2px #ddd');
        this.ctx = this.canvas.getContext('2d');
        this.maxX = 64;          // 最大行
        this.maxY = 40;          // 最大列
        this.itemWidth = 10;     // 每个点的大小
        this.direction = 'right'; // up down right left 方向
        this.speed = 150;        // ms 速度
        this.isStop = false;     // 是否暂停
        this.isOver = false;     // 是否结束
        this.isStart = false;    // 是否开始
        this.score = 0;          // 分数
        this.timer = null;       // 移动定时器
        this.j = 1;
        this.canChange = true;
    
        this.grid = new Array();
    
        // this.scoreDom = document.querySelector('#score');
        // this.mask = document.querySelector('#mask');
    
        for (let i = 0; i < this.maxX; i++) {
          for (let j = 0; j < this.maxY; j++) {
            this.grid.push([i, j]);
          }
        }
    
        this.drawGridLine();
        this.getDirection();
    
        document.addEventListener('keydown', (event) => {
          if (event.keyCode === 13) {
            if (!this.isStart) return;
            this.start();
          }
        });
      }
    
      // 开始
      start(): void {
        if (this.timer) {
          clearTimeout(this.timer);
        }
        if (!this.isStart) {
          this.isStart = true;
        }
        this.score = 0;
        this.speed = 150;
        this.isStop = false;
        this.isOver = false;
        this.direction = 'right';
        this.createSnake();
        this.createFood();
        this.draw();
        this.move();
        // this.mask.style.display = 'none';
      }
    
      // 创建蛇主体
      createSnake(): void {
        this.snake = [
          [4, 25],
          [3, 25],
          [2, 25],
          [1, 25],
          [0, 25]
        ];
      }
    
      // 移动
      move(): void {
        if (this.isStop) {
          return;
        }
    
        let [x, y] = this.snake[0];
        switch (this.direction) {
          case 'left':
            x--;
            break;
          case 'right':
            x++;
            break;
          case 'up':
            y--;
            break;
          case 'down':
            y++;
            break;
        }
    
        // 如果下一步不是食物的位置
        if (x !== this.food[0] || y !== this.food[1]) {
          this.snake.pop();
        } else {
          this.createFood();
        }
    
        if (this.over([x, y])) {
          this.isOver = true;
          // this.mask.style.display = 'block';
          // this.mask.innerHTML = '结束';
          return;
        }
        if (this.completed()) {
          // this.mask.style.display = 'block';
          // this.mask.innerHTML = '恭喜您,游戏通关';
          return;
        }
    
        this.snake.unshift([x, y]);
    
        this.draw();
        this.canChange = true;
        this.timer = setTimeout(() => this.move(), this.speed);
      }
    
      // 暂停游戏
      stop(): void {
        if (this.isOver) {
          return;
        }
        this.isStop = true;
        // this.mask.style.display = 'block';
        // this.mask.innerHTML = '暂停';
      }
    
      // 继续游戏
      continue(): void {
        if (this.isOver) {
          return;
        }
        this.isStop = false;
        this.move();
        // this.mask.style.display = 'none';
      }
    
      getDirection(): void {
        // 上38 下40 左37 右39 不能往相反的方向走
        document.onkeydown = (e) => {
          // 在贪吃蛇移动的间隔内不能连续改变两次方向
          if (!this.canChange) {
            return;
          }
          switch (e.keyCode) {
            case 37:
              if (this.direction !== 'right') {
                this.direction = 'left';
                this.canChange = false;
              }
              break;
            case 38:
              if (this.direction !== 'down') {
                this.direction = 'up';
                this.canChange = false;
              }
              break;
            case 39:
              if (this.direction !== 'left') {
                this.direction = 'right';
                this.canChange = false;
              }
              break;
            case 40:
              if (this.direction !== 'up') {
                this.direction = 'down';
                this.canChange = false;
              }
              break;
            case 32:
              // 空格暂停与继续
              if (!this.isStop) {
                this.stop();
              } else {
                this.continue();
              }
              break;
          }
        };
      }
      createPos(): any {
        // tslint:disable-next-line: no-bitwise
        const [x, y] = this.grid[(Math.random() * this.grid.length) | 0];
    
        for (const item of this.snake) {
          if (item[0] === x && item[1] === y) {
            return this.createPos();
          }
        }
        // for (let i = 0; i < this.snake.length; i++) {
        //   if (this.snake[i][0] === x && this.snake[i][1] === y) {
        //     return this.createPos();
        //   }
        // }
    
        return [x, y];
      }
      // 生成食物
      createFood(): void {
        this.food = this.createPos();
    
        // 更新分数
        // this.scoreDom.innerHTML = 'Score: ' + this.score++;
    
        if (this.speed > 50) {
          this.speed--;
        }
      }
    
      // 结束
      over([x, y]): boolean {
        if (x < 0 || x >= this.maxX || y < 0 || y >= this.maxY) {
          return true;
        }
    
        if (this.snake.some(v => v[0] === x && v[1] === y)) {
          return true;
        }
      }
    
      // 完成
      completed(): boolean {
        if (this.snake.length === this.maxX * this.maxY) {
          return true;
        }
      }
    
      // 网格线
      drawGridLine(): void {
        for (let i = 1; i < this.maxY; i++) {
          this.ctx.moveTo(0, i * this.itemWidth);
          this.ctx.lineTo(this.canvas.width, i * this.itemWidth);
        }
    
        for (let i = 1; i < this.maxX; i++) {
          this.ctx.moveTo(i * this.itemWidth, 0);
          this.ctx.lineTo(i * this.itemWidth, this.canvas.height);
        }
        this.ctx.lineWidth = 1;
        this.ctx.strokeStyle = '#ddd';
        this.ctx.stroke();
      }
    
      // 绘制
      draw(): void {
        // 清空画布
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    
        this.drawGridLine();
    
        this.ctx.fillStyle = '#000';
        this.ctx.fillRect(
          this.food[0] * this.itemWidth + this.j,
          this.food[1] * this.itemWidth + this.j,
          this.itemWidth - this.j * 2,
          this.itemWidth - + this.j * 2
        );
        // tslint:disable-next-line: no-bitwise
        this.j ^= 1;
    
        this.ctx.fillStyle = 'green';
        this.ctx.fillRect(
          this.snake[0][0] * this.itemWidth + 0.5,
          this.snake[0][1] * this.itemWidth + 0.5,
          this.itemWidth - 1,
          this.itemWidth - 1
        );
        this.ctx.fillStyle = 'red';
        for (let i = 1; i < this.snake.length; i++) {
          this.ctx.fillRect(
            this.snake[i][0] * this.itemWidth + 0.5,
            this.snake[i][1] * this.itemWidth + 0.5,
            this.itemWidth - 1,
            this.itemWidth - 1
          );
        }
      }
    }
    

    第三步:在Quill注册自定义Blot

    有了 SnakeBlot,还需要将其注册到 Quill 中才能使用:

    import SnakeBlot from './formats/snake';
    Quill.register('formats/snake', SnakeBlot);
    

    第四步:调用Quill的API插入自定义内容

    调用完API就可以玩贪吃蛇游戏啦,开心到飞起!

    const quill = new Quill('#editor', {
      theme: 'snow',
      modules: {
        toolbar: {
          container: TOOLBAR_CONFIG,
          handlers: {
            ...
            snake(value): void {
              console.log('snake~~');
              const index = this.quill.getSelection().index;
              // 插入自定义内容
              this.quill.insertEmbed(index, 'snake', {
                id: 'canvas-snake',
              });
            },
          },
        }
      },
    });
    

    效果图:

    今天是儿童节,整个贪吃蛇到编辑器里玩儿吧

    祝曾经是儿童的你儿童节快乐!

    欢迎加DevUI小助手微信:devui-official,一起讨论富文本编辑器技术和前端技术。

    欢迎关注我们DevUI组件库,点亮我们的小星星?:

    github.com/devcloudfe/…

    也欢迎使用DevUI新发布的DevUI Admin系统,开箱即用,10分钟搭建一个美观大气的后台管理系统!

    加入我们

    我们是DevUI团队,欢迎来这里和我们一起打造优雅高效的人机设计/研发体系。招聘邮箱:muyang2@huawei.com。

    文/DevUI Kagol

    往期文章推荐

    《如何将龙插入到编辑器中?》 《Quill富文本编辑器的实践》 《StepsGuide:一个像跟屁虫一样的组件》 《如何解决异步接口请求快慢不均导致的数据错误问题?》 《号外号外!DevUI Admin V1.0 发布啦!》


    起源地下载网 » 今天是儿童节,整个贪吃蛇到编辑器里玩儿吧

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元