npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@likg/match-line

v1.0.13

Published

js+canvas 连线题

Downloads

1

Readme

考试系统·连线题

推荐博文:稀土掘金·浅谈 js+canvas 实现考试系统·拖拽连线题

安装

$ npm i @likg/match-line
$ pnpm i @likg/match-line
$ yarn add @likg/match-line
import MatchLine from '@likg/match-line';
const matchLine = new MatchLine(options: MatchLineConfigs);

MatchLineOptions

连线题选项数据结构:

export type MatchLineOptions = Array<{
  leftOption: string;
  rightOption: string;
}>

示例如下:

[
  { leftOption: '水果', rightOption: '🥕' },
  { leftOption: '动物', rightOption: '🚗' },
  { leftOption: '汽车', rightOption: '🐒' },
  { leftOption: '蔬菜', rightOption: '🍌' },
]

MatchLineAnswers

答案数据结构:

export type MatchLineAnwsers = Record<string, string>;

示例如下:

{
  水果: '🥕',
  动物: '🚗',
  汽车: '🐒',
  蔬菜: '🍌',
}

MatchLineConfigs

| 属性 | 数据类型 | 描述 | | ---------------- | ----------------------------------------- | ------------------------------------------------------------ | | container | HTMLElement | 外层容器·包裹canvas和左右布局元素的容器布局结构:div.container>div.options+canvas+backCanvas | | canvas | HTMLCanvasElement | 实际连线标签Canvas | | backCanvas | HTMLCanvasElement | 模拟连线标签Canvas | | items | NodeListOf<HTMLElement> | 连线元素标签 | | itemActiveCls | string | 「可选」连线元素标签激活状态的类名,默认:active | | strokeStyle | string | CanvasGradient | CanvasPattern | 「可选」画笔颜色,默认:#6495ED | | lineWidth | number | 「可选」画笔粗细,默认:1 | | answers | MatchLineAnswers | 「可选」用户连线答案·可选(在查看试卷详情以及纠错时必传 | | standardAnswers | MatchLineAnswers | 「可选」标准答案(在纠错时必传) | | checkAnswers | boolean | 「可选」是否纠错(为true时必传answers 和 standardAnswers 字段) | | correctlineColor | string | 「可选」正确连线颜色,默认值:'#3CB371' | | mislineColor | string | 「可选」错误连线颜色,默认值:'#DC143C' | | disabled | boolean | 「可选」是否禁用(在查看试卷详情以及纠错时必传true) | | debug | boolean | 「可选」是否启用调式模式 | | onChange | (anwsers: MatchLineAnswers) => void | 「可选」每一次连线成功的回调·参数为连线结果集 |

APIs

  • matchLine.reset():重置
  • matchLine.undo():撤销
  • matchLine.getAnswers():获取连线结果
  • matchLine.checkAnswers():纠错

使用指南

布局

<div class="container">
	<!-- 工具栏 -->
	<div class="tools">
		<div class="button reset">重置</div>
		<div class="button undo">撤销</div>
		<div class="button check">纠错</div>
	</div>
	<div class="content">
		<!-- 左侧 -->
		<div class="list left">
			<div class="item" data-value="水果" data-ownership="L">水果</div>
			<div class="item" data-value="动物" data-ownership="L">动物</div>
			<div class="item" data-value="汽车" data-ownership="L">汽车</div>
			<div class="item" data-value="蔬菜" data-ownership="L">蔬菜</div>
		</div>
		<!-- 右侧 -->
		<div class="list right">
			<div class="item" data-value="🥕" data-ownership="R">🥕</div>
			<div class="item" data-value="🚗" data-ownership="R">🚗</div>
			<div class="item" data-value="🐒" data-ownership="R">🐒</div>
			<div class="item" data-value="🍌" data-ownership="R">🍌</div>
		</div>
		<!-- 实际连线标签 -->
		<canvas id="canvas" width="400" height="250"></canvas>
		<!-- 模拟连线标签 -->
		<canvas id="backCanvas" width="400" height="250"></canvas>
	</div>
</div>

提示:请严格按照上面的布局方式布局,连线元素必须设置 data-valuedata-ownership 属性,便于处理连线逻辑。

代码示例(react)

QuestionMatchLine.tsx

import React, { useEffect, useRef, useState } from 'react';
import './QuestionMatchLine.less';
import MatchLine from './m';

// -- 数据源
const dataSource = [
  { leftOption: '水果', rightOption: '🥕' },
  { leftOption: '动物', rightOption: '🚗' },
  { leftOption: '汽车', rightOption: '🐒' },
  { leftOption: '蔬菜', rightOption: '🍌' },
];

// -- 标准答案
const standardAnswers = {
  水果: '🍌',
  动物: '🐒',
  汽车: '🚗',
  蔬菜: '🥕',
};

const QuestionMatchLine: React.FC = React.memo(() => {
  const [matchLine, setMatchLine] = useState<MatchLine | null>(null);

  const containerRef = useRef<HTMLDivElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const backCanvasRef = useRef<HTMLCanvasElement | null>(null);

  useEffect(() => {
    // -- 初始化连线库
    if (canvasRef.current && backCanvasRef.current && containerRef.current) {
      const items = containerRef.current.querySelectorAll('.option');
      if (items.length > 0) {
        const matching = new MatchLine({
          container: containerRef.current,
          items: items as NodeListOf<HTMLElement>,
          canvas: canvasRef.current,
          backCanvas: backCanvasRef.current,
          itemActiveCls: 'active',
          standardAnswers,
          debug: true,
          onChange: (anwsers) => {
            console.log(anwsers);
          },
        });
        setMatchLine(matching);
      }
    }
  }, []);

  const renderItems = (ownership: 'L' | 'R') => {
    const k = ownership === 'L' ? 'leftOption' : 'rightOption';
    return dataSource.map((item, index) => (
      <div
        className="option"
        key={index}
        data-value={item[k]}
        data-ownership={ownership}
      >
        {item[k]}
      </div>
    ));
  };
  return (
    <div className="match-line">
      <div className="tools">
        <button onClick={() => matchLine?.reset()}>重置</button>
        <button onClick={() => matchLine?.undo()}>撤销</button>
        <button
          onClick={() => {
            const anwsers = matchLine?.getAnswers();
            console.log(anwsers);
          }}
        >
          查询
        </button>
        <button onClick={() => matchLine?.checkAnswers()}>纠错</button>
      </div>
      <div className="contents" ref={containerRef}>
        <div className="leftOptions">{renderItems('L')}</div>
        <div className="rightOptions">{renderItems('R')}</div>
        <canvas ref={canvasRef}></canvas>
        <canvas ref={backCanvasRef}></canvas>
      </div>
    </div>
  );
});

export default QuestionMatchLine;

QuestionMatchLine.less

.match-line {
  width: 400px;
  height: auto;
  margin: 100px auto;
  .tools {
    margin-bottom: 16px;
    button {
      margin-right: 8px;
    }
  }
  .contents {
    border: 1px dashed #ccc;
    position: relative;
    box-sizing: border-box;
    padding: 24px;
    display: flex;
    justify-content: space-between;
    align-items: centers;
    .option {
      width: 100px;
      height: 40px;
      background-color: #fff;
      display: flex;
      justify-content: center;
      align-items: center;
      border-radius: 4px;
      cursor: pointer;
      user-select: none;
      color: #555;
      position: relative;
      z-index: 1;
      &.active {
        background: #6495ed;
        color: #fff;
      }
      &:not(:last-child) {
        margin-bottom: 16px;
      }
    }
    canvas {
      position: absolute;
      top: 0;
      left: 0;
    }
  }
}

后续

我将持续优化升级 MatchLine,如果大家有什么疑问或建议欢迎留言。