资讯动态

vue-amap 中 el-amap-marker 自定义标注内容:content、template、contentRender 与 slots 四种渲染方式实战

发布时间:2026/10/14 17:36:35 来源:尧图企业网站定制
前端UI组件GIS【免费下载链接】vue-amap 基于 Vue 2.x 和高德地图的地图组件项目地址https://gitcode.com/gh_mirrors/vu/vue-amap点击查看免费下载本文聚焦 vue-amap基于 Vue 2.x 与高德地图 JSAPI 的地图组件库中el-amap-marker组件的自定义内容渲染能力在地图点标注上挂载任意 HTML、Vue 模板字符串、VNode 或具名 slot 内容。通过完整可运行的示例代码与 marker 组件源码 的逐层剖析你将掌握四种渲染方式的用法、版本支持要求、优先级规则以及自定义内容底层如何被转换为AMap.Marker的content选项并维持响应式更新。1. 场景与前置准备在地图应用中原生AMap.Marker的content选项接受 HTML 字符串或 DOM 节点但一旦内容需要响应 Vue 状态——例如标注上显示可点击的按钮、绑定点击事件、访问父组件的data和methods——就需要组件库做额外封装。vue-amap 为此提供了四种渲染方式渲染方式传入内容版本支持典型用途contentHTML 字符串 / DOM 节点基础能力纯静态标注内容templateVue 模板字符串v0.4.0 起需要 Vue 事件绑定、状态插值vnode/contentRenderVNode 或渲染函数v0.4.2 / v0.4.3 起组件化、JSX 风格、精细控制默认插槽slots模板中的子节点v0.4.5 起与常规 Vue 插槽用法一致的团队本仓库 package.json 当前版本为0.5.10以上版本能力均可使用。示例代码沿用了官方文档 marker 自定义内容示例 中的数据中心点[121.59996, 31.197646]上海缩放级别 1210 个沿纬度方向每隔0.03度分布的标注点。使用前需先按 快速上手 初始化全局 SDK 加载器之后el-amap与el-amap-marker才可用import Vue from vue; import VueAMap from vue-amap; Vue.use(VueAMap); VueAMap.initAMapApiLoader({ key: your amap key, plugin: [AMap.Autocomplete, AMap.PlaceSearch, AMap.Scale, AMap.OverView, AMap.ToolBar, AMap.MapType, AMap.PolyEditor, AMap.CircleEditor], v: 1.4.4 // 默认高德 SDK 版本 });地图容器需要有显式高度示例统一使用 300pxtemplate div classamap-page-container el-amap vidamapDemo :centercenter :zoomzoom classamap-demo !-- 各类 el-amap-marker 作为子节点 -- /el-amap /div /template style .amap-demo { height: 300px; } /style2. 方式一content基础渲染content是最底层的方式直接把 HTML 字符串或 DOM 对象作为标注内容传给AMap.Marker。根据 点坐标markerAPI 文档 的动态属性说明content有效时会覆盖icon属性——即有合法 content 内容时icon 属性无效。适合纯静态内容例如只展示一段文本或简单 div不需要交互时选它最省事export default { data() { const center [121.59996, 31.197646]; return { zoom: 12, center, markers: [] }; }, created() { const basePosition [121.59996, 31.197646]; const num 10; const markers []; for (let i 0; i num; i) { markers.push({ position: [basePosition[0], basePosition[1] i * 0.03], content: content ${i} }); } this.markers markers; } };el-amap-marker v-for(marker, index) in markers :positionmarker.position :vidindex :contentmarker.content/el-amap-marker从源码看content属于普通 prop没有经过任何 converter在 register-component.js 的convertSignalProp中会原样透传给AMap.Marker的content选项因此它不具备响应 Vue 状态的能力——字符串一旦传入后续插值不会变化虽然 prop 本身是响应式监听的但 HTML 字符串不具备模板语义。3. 方式二template模板字符串渲染v0.4.0 起template接收一个Vue 模板字符串支持 Vue 机制的事件绑定和状态访问。官方示例中每个标注渲染为一个按钮按钮文本插值父组件的source状态点击触发父组件的handler方法export default { data() { return { zoom: 12, center: [121.59996, 31.197646], markers: [], source: click }; }, created() { const basePosition [121.59996, 31.197646]; const num 10; const markers []; for (let i 0; i num; i) { markers.push({ position: [basePosition[0], basePosition[1] i * 0.03], template: button clickhandler(${i}){{ source }} ${i}/button }); } this.markers markers; }, methods: { handler(index) { alert(${index} - trigger); } } };el-amap-marker v-formarker in markers :positionmarker.position :templatemarker.template/el-amap-marker模板是如何被编译挂载的template的编译逻辑在 compile.js 的compile函数中这是理解该方式能力边界的关键export const compile (tpl, vm) { let keys [methods, computed, data, filters]; let props {}; let node Vue.compile(tpl); keys.forEach(key { props[key] vm.$parent.$parent.$options[key]; if (key data typeof props[key] function) { props[key] props[key](); } }); let vNode new Vue({ ...props, ...node }); vNode.$mount(); return vNode; };从源码结构看compile会为每个标注模板新建一个临时 Vue 实例并且这个实例的methods、computed、data、filters全部取自vm.$parent.$parent.$options——即 marker 组件的祖父级组件通常是使用el-amap的业务页面组件。这解释了示例中为什么模板里的handler(i)和{{ source }}能直接访问到父组件的方法与数据不是作用域魔法而是组件库显式把业务组件的选项搬进了临时实例。因此模板内可用的状态和方法以el-amap所在组件的选项为准每个标注都会产生一个独立的临时实例组件销毁时由 amap-marker.vue 的 destroyed 钩子 调用$destroy()清理避免内存泄漏标注较多时成百上千意味着同样数量的临时实例性能敏感场景建议优先考虑content或聚合方案。在 amap-marker.vue 的 converters 中template转换器调用compile后返回template.$elDOM 元素作为AMap.Marker的contenttemplate(tpl) { const template compile(tpl, self); this.$customContent template; return template.$el; }this.$customContent持有临时实例引用正是组件销毁时执行$destroy()的依据。4. 方式三contentRender与vnode渲染v0.4.3 / v0.4.2 起当内容本身就是一个组件或需要更精细的 VNode 控制时contentRender渲染函数与vnode是更现代的选择。官方示例展示了两种写法渲染带props的BtnComponent子组件含nativeOn原生事件以及直接h(button, ...)构造裸 VNodeconst BtnComponent { props: [text], template: button{{text}}/button }; data() { const center [121.59996, 31.197646]; const componentsMarkers [1, 2, 3, 4].map((item, index) ({ position: [center[0] index * 0.02, center[1] index * 0.02], vid: ${index}-vid, contentRender: h h(BtnComponent, { props: { text: component ${index} }, style: { background: rgb(173, 47, 47), color: #eee }, nativeOn: { click: () this.handler(component-${index}) } }) })); return { zoom: 12, center, componentsMarkers }; }, methods: { handler(index) { alert(${index} - trigger); } }el-amap-marker v-for(marker, index) in componentsMarkers :positionmarker.position :vidmarker.vid :content-rendermarker.contentRender/el-amap-marker两种属性在源码中的处理路径不同见 amap-marker.vue 的 convertersvnode(vnode) { const _vNode typeof vnode function ? vnode(self) : vnode; const vNode mountedVNode(_vNode); this.$customContent vNode; return vNode.$el; }, contentRender(renderFn) { const template mountedRenderFn(renderFn, self); this.$customContent template; return template.$el; }对应的实现都在 compile.jsexport const mountedVNode (vn) { const instance new Vue({render: (h) h(div, vn)}); instance.$mount(); return instance; }; export const mountedRenderFn (renderFn, vueInstance) { const instance new Vue({render: h renderFn(h, vueInstance)}); instance.$mount(); return instance; };两者的差异在于作用域vnode接受 VNode 或(instance) VNode函数被包进一个div容器后挂载渲染时拿不到父组件上下文contentRender的渲染函数签名是(h, instance) VNode见 marker.md 动态属性表第二个参数就是传入的 marker 组件实例本身self所以示例中可以直接this.handler(...)、访问业务组件的方法与数据若项目使用 JSX也可以在contentRender中直接写 JSX 返回元素marker 基础示例的注释中也给出了该用法提示。注意contentRender的函数体内是同步执行于 converter 调用时机的箭头函数其中引用的this是定义处的业务组件data()内这是示例能直接this.handler的原因若改在methods中定义回调则需自行绑定。5. 方式四默认插槽slots渲染v0.4.5 起插槽方式最贴近 Vue 惯用法在el-amap-marker标签内直接写子模板随v-for循环自然复用export default { data() { const center [121.59996, 31.197646]; return { zoom: 12, center, markers: [], count: 0, slotStyle: { padding: 2px 8px, background: #eee, color: #333, border: 1px solid #aaa } }; }, methods: { onClick() { this.count 1; } }, created() { const basePosition [121.59996, 31.197646]; const num 10; const markers []; for (let i 0; i num; i) { markers.push({ position: [basePosition[0], basePosition[1] i * 0.03] }); } this.markers markers; } };el-amap-marker v-for(marker, index) in markers :positionmarker.position :vidindex div :styleslotStyle bHello {{ count }} times/b button clickonClickAdd/button /div /el-amap-marker插槽之所以能随 Vue 响应源于 amap-marker.vue 的一个巧妙设计组件在created时预建了一个挂载好的宿主 Vue 实例tmpVM其渲染函数渲染一个可变的node数组marker 组件自己的render(h)把$slots.default写回tmpVM.node从而让插槽 vnode 进入一个独立的、已挂载的虚拟 DOM 树中created() { this.tmpVM new Vue({ data() { return {node: }; }, render(h) { const {node} this; return h(div, {ref: node}, Array.isArray(node) ? node : [node]); } }).$mount(); }, render(h) { const slots this.$slots.default || []; if (slots.length) { this.tmpVM.node slots; } return null; }随后在__initComponent中只要检测到默认插槽存在就把tmpVM.$refs.node一个真实 DOM 节点作为AMap.Marker的content选项注入__initComponent(options) { if (this.$slots.default this.$slots.default.length) { options.content this.tmpVM.$refs.node; } this.$amapComponent new AMap.Marker(options); }由于插槽 vnode 来自业务组件的渲染上下文{{ count }}插值和click事件天然保持响应式——点击每个标注上的 Add 按钮都会使count自增并触发重新渲染这是插槽方式区别于content字符串的核心优势也是它不需要像template那样借用祖父组件选项的原因。6. 底层机制自定义内容如何进入 AMap.Marker四种方式最终都归一为AMap.Marker的content选项amap-marker.vue 通过propsRedirect显式声明了这一收敛关系propsRedirect: { template: content, vnode: content, contentRender: content }完整调用链如下关键步骤均有源码出处属性转换register-component.js 的 convertProps 遍历propsData对template/vnode/contentRender重定向为content键并把值交给 convertSignalProp 中的 convertercompile/mountedVNode/mountedRenderFn生成 DOM 节点实例创建__initComponent以convertProps()的结果含map、position、content等执行new AMap.Marker(options)position会经 convert-helper.js 的toLngLat转换为AMap.LngLat注册与监听registerRest 依次完成事件注册events/onceEventsprop、visible等属性初始化以及setPropWatchers——对每个 prop 建立$watch值变化时调用set 属性名形式的高德 setter如setContent因此这些内容属性同时支持响应式更新管理器登记vid属性让 marker 被登记进 AMapManager见 registerToManager后续可通过amapManager.getComponent(vid)反向拿到底层AMap.Marker实例amap 组件单元测试 验证了AMapManager与vid的联动机制地图就绪时序覆盖物依赖父级地图实例。amap.vue 的 createMap 创建AMap.Map后通过AMAP_READY_EVENT事件通知子组件register-component.js 的 mounted 钩子 在收到事件后才执行register()——这保证了自定义内容一定在地图就绪后才挂载销毁清理destroyed 钩子 销毁tmpVM与$customContent临时实例register-component的destroyed则执行setMap(null)并注销所有 watcher 与事件监听。7. 优先级、选型与注意事项优先级当同时设置content与template时content优先官方示例文档的明确约定。propsRedirect的重写顺序决定了convertProps的最终取值业务代码应只保留一种写法避免歧义与 icon 的关系content有效时icon属性被覆盖二者不要混用选型建议静态文本/图片标注 →content零额外开销需要插值和事件、且不想写渲染函数 →template注意它基于祖父组件选项借用状态标注内容本身就是 Vue 组件、或需要 JSX / 精细 VNode 控制 →contentRender/vnode团队习惯插槽写法、内容与标注一一对应 → 默认插槽推荐优先尝试响应式最自然版本前提template需 v0.4.0、vnode需 v0.4.2、contentRender需 v0.4.3、slots 需 v0.4.5本仓库 0.5.10 全部满足项目依赖 Vue 2.x初始化前必须调用VueAMap.initAMapApiLoader并配置有效的 AMap key规模提示template/vnode/contentRender每种方式都会为标注创建独立的临时 Vue 实例v-for批量渲染大量标注时应评估实例数量必要时配合聚合仓库示例中心提供 marker-cluster 示例见 marker 聚合示例。8. 关键文件索引文件说明src/docs/zh-cn/examples/coverings/marker-custom-content.md本文对应的官方示例文档content / template / render / slots 四节src/lib/components/amap-marker.vuemarker 组件props 声明、converters、slots 宿主实例与生命周期清理src/lib/utils/compile.jscompile/mountedVNode/mountedRenderFn三个渲染工具src/lib/mixins/register-component.js属性转换、响应式监听、事件与 manager 注册的公共 mixinsrc/docs/zh-cn/coverings/marker.mdmarker 完整 API静态/动态属性表、ref 方法、事件表src/lib/managers/amap-manager.js基于vid的组件管理器src/docs/zh-cn/introduction/quick-start.md安装与initAMapApiLoader初始化赞分享前端UI组件GIS【免费下载链接】vue-amap 基于 Vue 2.x 和高德地图的地图组件项目地址https://gitcode.com/gh_mirrors/vu/vue-amap点击查看免费下载相关推荐vue-amap 点标记覆盖物 el-amap-marker从基础用法、自定义内容到响应式原理详解vue amap 点标记覆盖物 el amap marker从基础用法、自定义内容到响应式原理详解 本文以 vue amap 的覆盖物组件 el amap m前端UI组件GISvue-amap 圆点标记el-amap-circle-marker组件完全指南响应式属性、事件与源码级原理vue amap 圆点标记el amap circle marker组件完全指南响应式属性、事件与源码级原理 本指南以 vue amap 官方文档 cir前端UI组件GISvue-amap el-amap-rectangle 矩形覆盖物组件属性、事件与源码实现详解vue amap el amap rectangle 矩形覆盖物组件属性、事件与源码实现详解 本篇围绕 vue amap 的矩形覆盖物组件 el amap r前端UI组件GIS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑