Ember Modifier修饰符 #
一、Modifier概述 #
Modifier是Ember中用于直接操作DOM元素的工具。它们类似于Vue的指令或React的ref,用于:
- DOM属性操作
- 事件监听
- 第三方库集成
- 焦点管理
- 动画效果
1.1 生成Modifier #
bash
ember generate modifier autofocus
生成文件:
text
app/modifiers/autofocus.js
tests/integration/modifiers/autofocus-test.js
二、基本Modifier #
2.1 函数式Modifier #
javascript
// app/modifiers/autofocus.js
import { modifier } from 'ember-modifier';
export default modifier(function autofocus(element, positional, { delay = 0 }) {
setTimeout(() => {
element.focus();
}, delay);
});
handlebars
{{! 使用Modifier}}
<input {{autofocus}} />
{{! 带参数}}
<input {{autofocus delay=100}} />
2.2 Class-based Modifier #
javascript
// app/modifiers/tooltip.js
import Modifier from 'ember-modifier';
import { tracked } from '@glimmer/tracking';
export default class TooltipModifier extends Modifier {
@tracked tooltip;
modify(element, [text], { position = 'top' }) {
this.tooltip = this.createTooltip(element, text, position);
}
createTooltip(element, text, position) {
const tooltip = document.createElement('div');
tooltip.className = `tooltip tooltip-${position}`;
tooltip.textContent = text;
tooltip.style.display = 'none';
element.appendChild(tooltip);
element.addEventListener('mouseenter', () => {
tooltip.style.display = 'block';
});
element.addEventListener('mouseleave', () => {
tooltip.style.display = 'none';
});
return tooltip;
}
remove() {
if (this.tooltip) {
this.tooltip.remove();
}
}
}
handlebars
<button {{tooltip "点击提交表单" position="bottom"}}>
提交
</button>
三、常用Modifier示例 #
3.1 自动聚焦 #
javascript
// app/modifiers/autofocus.js
import { modifier } from 'ember-modifier';
export default modifier(function autofocus(element, [condition = true], { delay = 0 }) {
if (!condition) return;
const timeoutId = setTimeout(() => {
element.focus();
}, delay);
return () => clearTimeout(timeoutId);
});
handlebars
{{! 始终聚焦}}
<input {{autofocus}} />
{{! 条件聚焦}}
<input {{autofocus @isOpen}} />
{{! 延迟聚焦}}
<input {{autofocus delay=500}} />
3.2 点击外部 #
javascript
// app/modifiers/click-outside.js
import { modifier } from 'ember-modifier';
export default modifier(function clickOutside(element, [callback]) {
function handleClick(event) {
if (!element.contains(event.target)) {
callback(event);
}
}
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
});
handlebars
<div {{click-outside this.closeDropdown}}>
{{! 下拉菜单内容}}
</div>
3.3 滚动到元素 #
javascript
// app/modifiers/scroll-into-view.js
import { modifier } from 'ember-modifier';
export default modifier(function scrollIntoView(element, [shouldScroll], { behavior = 'smooth', block = 'start' }) {
if (shouldScroll) {
element.scrollIntoView({ behavior, block });
}
});
handlebars
<div {{scroll-into-view @shouldScroll behavior="smooth" block="center"}}>
目标内容
</div>
3.4 事件监听 #
javascript
// app/modifiers/on-key.js
import { modifier } from 'ember-modifier';
export default modifier(function onKey(element, [key, callback], { event = 'keydown' }) {
function handleKey(e) {
if (e.key === key) {
callback(e);
}
}
document.addEventListener(event, handleKey);
return () => {
document.removeEventListener(event, handleKey);
};
});
handlebars
<div {{on-key "Escape" this.closeModal}}>
{{! 模态框内容}}
</div>
3.5 样式绑定 #
javascript
// app/modifiers/style.js
import { modifier } from 'ember-modifier';
export default modifier(function style(element, [], styles) {
Object.entries(styles).forEach(([property, value]) => {
element.style[property] = value;
});
});
handlebars
<div {{style color=this.textColor backgroundColor=this.bgColor}}>
内容
</div>
3.6 类名绑定 #
javascript
// app/modifiers/class.js
import { modifier } from 'ember-modifier';
export default modifier(function classModifier(element, [], classMap) {
Object.entries(classMap).forEach(([className, condition]) => {
if (condition) {
element.classList.add(className);
} else {
element.classList.remove(className);
}
});
});
handlebars
<div {{class active=this.isActive disabled=this.isDisabled}}>
内容
</div>
四、第三方库集成 #
4.1 集成Chart.js #
javascript
// app/modifiers/chart.js
import Modifier from 'ember-modifier';
import Chart from 'chart.js/auto';
export default class ChartModifier extends Modifier {
chart;
modify(element, [config], { type = 'line' }) {
this.destroyChart();
this.chart = new Chart(element, {
type,
data: config.data,
options: config.options,
});
}
destroyChart() {
if (this.chart) {
this.chart.destroy();
}
}
remove() {
this.destroyChart();
}
}
handlebars
<canvas {{chart this.chartConfig type="bar"}}></canvas>
4.2 集成Trix编辑器 #
javascript
// app/modifiers/trix-editor.js
import Modifier from 'ember-modifier';
import 'trix';
import 'trix/dist/trix.css';
export default class TrixEditorModifier extends Modifier {
modify(element, [], { value, onChange }) {
element.value = value || '';
if (onChange) {
element.addEventListener('trix-change', (e) => {
onChange(e.target.value);
});
}
}
}
handlebars
<trix-editor {{trix-editor value=@content onChange=this.handleContentChange}}></trix-editor>
4.3 集成IntersectionObserver #
javascript
// app/modifiers/in-viewport.js
import Modifier from 'ember-modifier';
export default class InViewportModifier extends Modifier {
observer;
modify(element, [callback], { threshold = 0.5, rootMargin = '0px' }) {
this.observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
callback(entry.isIntersecting, entry);
});
},
{ threshold, rootMargin }
);
this.observer.observe(element);
}
remove() {
if (this.observer) {
this.observer.disconnect();
}
}
}
handlebars
<div {{in-viewport this.onVisibilityChange threshold=0.5}}>
{{! 懒加载内容}}
</div>
五、内置修饰符 #
5.1 on修饰符 #
handlebars
{{! 事件监听}}
<button {{on "click" this.handleClick}}>
点击
</button>
{{! 带选项}}
<input {{on "input" this.handleInput capture=true passive=true}} />
{{! 多个事件}}
<div
{{on "mouseenter" this.handleMouseEnter}}
{{on "mouseleave" this.handleMouseLeave}}
>
悬停区域
</div>
5.2 fn修饰符 #
handlebars
{{! 函数绑定}}
<button {{on "click" (fn this.delete @item.id)}}>
删除
</button>
{{! 部分应用}}
<button {{on "click" (fn this.update "status" "active")}}>
激活
</button>
5.3 prevent-default修饰符 #
handlebars
{{! 阻止默认行为}}
<form {{on "submit" (prevent-default this.handleSubmit)}}>
<button type="submit">提交</button>
</form>
5.4 stop-propagation修饰符 #
handlebars
{{! 阻止事件冒泡}}
<div {{on "click" this.handleOuterClick}}>
<button {{on "click" (stop-propagation this.handleInnerClick)}}>
内部按钮
</button>
</div>
六、Modifier最佳实践 #
6.1 清理资源 #
javascript
// 好的做法 - 清理事件监听
export default modifier(function resize(element, [callback]) {
window.addEventListener('resize', callback);
return () => window.removeEventListener('resize', callback);
});
// 避免 - 不清理
export default modifier(function resize(element, [callback]) {
window.addEventListener('resize', callback);
// 没有清理,可能导致内存泄漏
});
6.2 参数验证 #
javascript
export default modifier(function tooltip(element, [text], { position = 'top' }) {
if (!text) {
console.warn('tooltip modifier requires text');
return;
}
// 创建tooltip...
});
6.3 响应式更新 #
javascript
export default class ActiveModifier extends Modifier {
modify(element, [isActive]) {
if (isActive) {
element.classList.add('active');
} else {
element.classList.remove('active');
}
}
}
七、测试Modifier #
7.1 集成测试 #
javascript
// tests/integration/modifiers/autofocus-test.js
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, focus } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Modifier | autofocus', function (hooks) {
setupRenderingTest(hooks);
test('it focuses the element', async function (assert) {
await render(hbs`
<input id="first" />
<input id="second" {{autofocus}} />
`);
assert.dom('#second').isFocused('second input should be focused');
});
test('it respects delay option', async function (assert) {
await render(hbs`
<input {{autofocus delay=50}} />
`);
// 等待延迟
await new Promise((resolve) => setTimeout(resolve, 100));
assert.dom('input').isFocused();
});
});
八、常用Modifier集合 #
8.1 表单相关 #
javascript
// app/modifiers/autofocus.js - 自动聚焦
// app/modifiers/autoselect.js - 自动选择文本
export default modifier(function autoselect(element) {
element.select();
});
// app/modifiers/submit-on-enter.js - 回车提交
export default modifier(function submitOnEnter(element, [callback]) {
function handleKeydown(e) {
if (e.key === 'Enter') {
e.preventDefault();
callback();
}
}
element.addEventListener('keydown', handleKeydown);
return () => element.removeEventListener('keydown', handleKeydown);
});
8.2 UI交互 #
javascript
// app/modifiers/draggable.js - 拖拽
// app/modifiers/resizable.js - 调整大小
// app/modifiers/sticky.js - 粘性定位
8.3 动画 #
javascript
// app/modifiers/fade-in.js - 淡入
export default modifier(function fadeIn(element, [duration = 300]) {
element.style.opacity = '0';
element.style.transition = `opacity ${duration}ms`;
requestAnimationFrame(() => {
element.style.opacity = '1';
});
});
九、总结 #
Modifier是Ember中操作DOM的推荐方式:
| 类型 | 用途 | 示例 |
|---|---|---|
| 函数式 | 简单操作 | {{autofocus}} |
| Class-based | 复杂逻辑 | {{tooltip "提示"}} |
| 内置 | 常用功能 | {{on "click" fn}} |
合理使用Modifier可以让DOM操作更加模块化和可复用。
最后更新:2026-03-28