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 🙏

© 2026 – Pkg Stats / Ryan Hefner

hand-painted-map

v1.0.4

Published

一个基于 Vue 的地图组件

Readme

Vue 3 + TypeScript + Vite

主要适用于:设备版本过低、内容自定义过高、图片加载速度慢等场景

Canvas 地图组件

这是一个基于Canvas的高性能地图组件,支持图片底图、缩放、拖拽、路线绘制、标记点等功能。

功能特性

  • 🖼️ 图片底图支持 - 支持任意尺寸的图片作为地图底图
  • 🔍 缩放功能 - 鼠标滚轮或按钮控制缩放,支持自定义缩放范围
  • 🖱️ 拖拽功能 - 鼠标拖拽移动地图,支持触摸设备
  • 🛣️ 路线绘制 - 支持绘制多条路线,自定义颜色和宽度
  • 📍 标记点 - 支持添加标记点,点击显示弹窗
  • 🎬 分割动画 - 从中间分割成两半,分别向左右移动,中间留出间隙,然后合并成完整地图
  • 📱 响应式设计 - 自适应容器大小变化,支持响应式图片尺寸
  • 🖥️ 屏幕适配 - 监听屏幕变化,自动调整Canvas和图片尺寸
  • 🖼️ 完全铺满 - 图片和Canvas完全铺满容器,宽度和高度都100%覆盖,不显示背景色
  • 高性能 - 基于Canvas渲染,流畅的交互体验

组件结构

├── CanvasMap.vue      # 核心地图组件

使用方法

基本使用

<template>
  <CanvasMap
    :image-url="imageUrl"
    :image-width="7158"
    :image-height="5010"
    :lines="lines"
    :markers="markers"
    :min-zoom="1"
    :max-zoom="3"
    :initial-zoom="1"
    :enable-animation="true"
    :animation-duration="2000"
    :responsive-image="true"
    :min-scale="1"
    :max-scale="3"
    :split-gap="500"           <!-- 可选:分割动画间隙,不传为经典动画 -->
    :bg-image-url="bgImageUrl" <!-- 可选:背景图片,不传为六边形渐变背景 -->
    @marker-click="handleMarkerClick"
    @map-click="handleMapClick"
  >
    <!-- 自定义弹窗内容 -->
    <template #popup="{ marker, close, position }">
      <div class="custom-popup">
        <h3>{{ marker?.title }}</h3>
        <p>{{ marker?.description }}</p>
        <button @click="close">关闭</button>
      </div>
    </template>
  </CanvasMap>
</template>

<script setup>
import { ref } from 'vue'
import CanvasMap from './components/CanvasMap.vue'

const imageUrl = '/src/assets/imgMap.png'
const bgImageUrl = '' // 或 'https://pic1.imgdb.cn/item/663cc08b0ea9cb14039b7ded.png'

const markers = ref([
  {
    id: 1,
    position: { x: 100, y: 100 },
    title: '标记点1',
    description: '这是第一个标记点'
  }
])

const lines = ref([
  {
    id: 1,
    points: [
      { x: 100, y: 100 },
      { x: 200, y: 150 },
      { x: 300, y: 200 }
    ],
    color: '#f56c6c',
    width: 8
  }
])

const handleMarkerClick = (marker) => {
  console.log('点击了标记点:', marker)
}

const handleMapClick = (position) => {
  console.log('点击了地图位置:', position)
}
</script>

组件属性

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | imageUrl | string | '/src/assets/imgMap.png' | 地图底图URL | | imageWidth | number | 7158 | 图片宽度(像素) | | imageHeight | number | 5010 | 图片高度(像素) | | lines | Line[] | [] | 路线数据,见下方Line类型 | | markers | Marker[] | [] | 标记点数据,见下方Marker类型 | | minZoom | number | 1 | 最小缩放比例(缩放不会小于此值) | | maxZoom | number | 3 | 最大缩放比例(缩放不会大于此值) | | initialZoom | number | 1 | 初始缩放比例 | | enableAnimation | boolean | true | 是否启用分割动画 | | animationDuration | number | 2000 | 分割动画持续时间(毫秒) | | responsiveImage | boolean | false | 是否自适应容器尺寸铺满 | | minScale | number | 1 | 最小缩放倍数(与minZoom类似,优先级高于minZoom) | | maxScale | number | 3 | 最大缩放倍数(与maxZoom类似,优先级高于maxZoom) | | splitGap | number/undefined | undefined | 分割动画中间最大间隙(像素),不传时为经典分割动画,传递时为自定义间隙 | | bgImageUrl | string | '' | 地图容器背景图片URL,不传时为六边形深色渐变背景,传递时为图片背景 |

数据类型

Line 路线

interface Line {
  id: number
  points: Point[]        // 路线点数组
  color?: string         // 路线颜色,默认 '#f56c6c'
  width?: number         // 路线宽度,默认 10
}

Marker 标记点

interface Marker {
  id: number
  position: Point        // 标记点位置
  icon?: string          // 图标URL(可选)
  title?: string         // 标题
  description?: string   // 描述
}

Point 坐标点

interface Point {
  x: number
  y: number
}

事件

| 事件 | 参数 | 说明 | |------|------|------| | marker-click | marker: Marker | 点击标记点时触发 | | map-click | position: Point | 点击地图时触发 |

插槽 (Slots)

| 插槽名 | 作用域参数 | 说明 | |--------|------------|------| | popup | { marker, close, position } | 自定义弹窗内容 |

插槽参数说明

  • marker: 当前选中的标记点对象
  • close: 关闭弹窗的函数
  • position: 弹窗的屏幕坐标位置

插槽使用示例

<template #popup="{ marker, close, position }">
  <div class="custom-popup">
    <div class="popup-header">
      <h3>{{ marker?.title }}</h3>
      <button @click="close">×</button>
    </div>
    <div class="popup-body">
      <p>{{ marker?.description }}</p>
      <p>位置: ({{ marker?.position.x }}, {{ marker?.position.y }})</p>
      <button @click="handleAction(marker)">操作</button>
    </div>
  </div>
</template>

方法

通过ref可以调用以下方法:

<template>
  <CanvasMap ref="mapRef" />
</template>

<script setup>
const mapRef = ref()

// 获取当前缩放比例
const zoom = mapRef.value?.zoom()

// 设置缩放比例
mapRef.value?.setZoom(1.5)

// 移动到指定位置
mapRef.value?.panTo(100, 200)

// 适应边界
mapRef.value?.fitBounds({
  minX: 0,
  minY: 0,
  maxX: 1000,
  maxY: 1000
})

// 播放分割动画
mapRef.value?.playAnimation()

// 检查动画状态
const isAnimating = mapRef.value?.isAnimating()
</script>

与原始实现的对比

1.0 (DOM版本)

  • ✅ 支持缩放和拖拽
  • ✅ 支持路线绘制
  • ✅ 支持标记点
  • ❌ 路线连接处有空白问题
  • ❌ 性能较差

1.0.1 (Canvas版本)

  • ✅ 路线绘制完美
  • ✅ 性能优秀
  • ❌ 无法拖拽和缩放
  • ❌ 交互功能缺失

1.0.2 (新版本)

  • ✅ 结合了两种实现的优点
  • ✅ 完美的路线绘制
  • ✅ 完整的交互功能
  • ✅ 高性能Canvas渲染
  • ✅ 响应式设计

效果视频

点击观看

开发说明

  1. 组件使用Canvas 2D API进行渲染
  2. 支持鼠标和触摸事件
  3. 实现了边界约束,防止地图移出可视区域
  4. 使用Vue 3 Composition API开发
  5. 完全支持TypeScript

运行项目

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

# 构建生产版本
pnpm build

访问 http://localhost:5173 查看演示效果。