4.1 事件系统概述
Sciter事件模型
Sciter采用了类似于Web浏览器的事件模型,但针对桌面应用进行了优化和扩展。事件系统是用户界面交互的核心,它允许应用程序响应用户操作、系统消息和自定义事件。
事件流程
- 事件产生:用户操作或系统触发
- 事件捕获:从根元素向目标元素传播
- 事件处理:在目标元素上执行处理函数
- 事件冒泡:从目标元素向根元素传播
- 默认行为:执行元素的默认行为(可阻止)
事件类型
- 鼠标事件:点击、移动、滚轮等
- 键盘事件:按键按下、释放等
- 焦点事件:获得焦点、失去焦点
- 表单事件:输入、提交、重置等
- 窗口事件:调整大小、关闭等
- 自定义事件:应用程序定义的事件
4.2 DOM事件处理
事件绑定方法
1. HTML内联事件处理
<!-- 直接在HTML中定义事件处理 -->
<button onclick="handleClick(this)">点击我</button>
<input onchange="handleInputChange(this)" />
<div onmouseover="showTooltip(this)" onmouseout="hideTooltip(this)">
悬停显示提示
</div>
<script type="text/tiscript">
function handleClick(element) {
stdout.println("按钮被点击: " + element.innerText);
}
function handleInputChange(element) {
stdout.println("输入值改变: " + element.value);
}
function showTooltip(element) {
element.attributes["title"] = "这是一个提示";
}
function hideTooltip(element) {
element.attributes.removeNamedItem("title");
}
</script>
2. 脚本事件绑定
// 使用on方法绑定事件
function self.ready() {
// 单个元素事件绑定
var button = $("#my-button");
button.on("click", function(evt) {
stdout.println("按钮被点击");
stdout.println("事件类型: " + evt.type);
stdout.println("目标元素: " + evt.target.tag);
});
// 多个事件绑定
var input = $("#my-input");
input.on("focus blur", function(evt) {
if (evt.type == "focus") {
this.addClass("focused");
} else {
this.removeClass("focused");
}
});
// 事件委托(为动态元素绑定事件)
$("#container").on("click", "button", function(evt) {
stdout.println("容器内的按钮被点击: " + this.innerText);
});
// 一次性事件
$("#one-time-button").on("click.once", function(evt) {
stdout.println("这个事件只会触发一次");
});
}
// 移除事件监听
function removeEventListeners() {
$("#my-button").off("click");
$("#my-input").off(); // 移除所有事件
}
3. 事件处理函数详解
// 完整的事件处理函数
function handleComplexEvent(evt) {
// 事件基本信息
stdout.println("事件类型: " + evt.type);
stdout.println("目标元素: " + evt.target.tag);
stdout.println("当前元素: " + evt.currentTarget.tag);
stdout.println("时间戳: " + evt.timeStamp);
// 鼠标事件信息
if (evt.type.indexOf("mouse") >= 0) {
stdout.println("鼠标位置: (" + evt.clientX + ", " + evt.clientY + ")");
stdout.println("屏幕位置: (" + evt.screenX + ", " + evt.screenY + ")");
stdout.println("按键状态: Ctrl=" + evt.ctrlKey + ", Shift=" + evt.shiftKey + ", Alt=" + evt.altKey);
stdout.println("鼠标按键: " + evt.button);
}
// 键盘事件信息
if (evt.type.indexOf("key") >= 0) {
stdout.println("按键代码: " + evt.keyCode);
stdout.println("字符代码: " + evt.charCode);
stdout.println("按键: " + evt.key);
stdout.println("修饰键: Ctrl=" + evt.ctrlKey + ", Shift=" + evt.shiftKey + ", Alt=" + evt.altKey);
}
// 阻止默认行为
if (evt.type == "contextmenu") {
evt.preventDefault();
stdout.println("阻止了右键菜单");
}
// 停止事件传播
if (evt.target.hasClass("stop-propagation")) {
evt.stopPropagation();
stdout.println("停止事件冒泡");
}
}
常用事件类型
1. 鼠标事件
// 鼠标事件处理
function setupMouseEvents() {
var element = $("#interactive-area");
// 鼠标点击事件
element.on("click", function(evt) {
stdout.println("单击: 按键=" + evt.button);
});
element.on("dblclick", function(evt) {
stdout.println("双击");
});
element.on("contextmenu", function(evt) {
stdout.println("右键菜单");
// 显示自定义菜单
showCustomMenu(evt.clientX, evt.clientY);
evt.preventDefault();
});
// 鼠标按下和释放
element.on("mousedown", function(evt) {
this.addClass("pressed");
stdout.println("鼠标按下");
});
element.on("mouseup", function(evt) {
this.removeClass("pressed");
stdout.println("鼠标释放");
});
// 鼠标移动事件
element.on("mousemove", function(evt) {
// 节流处理,避免过多事件
if (!this._lastMoveTime || Date.now() - this._lastMoveTime > 100) {
this._lastMoveTime = Date.now();
updateMousePosition(evt.clientX, evt.clientY);
}
});
// 鼠标进入和离开
element.on("mouseenter", function(evt) {
this.addClass("hover");
showTooltip(this);
});
element.on("mouseleave", function(evt) {
this.removeClass("hover");
hideTooltip();
});
// 鼠标滚轮事件
element.on("wheel", function(evt) {
var delta = evt.deltaY;
if (delta > 0) {
stdout.println("向下滚动");
} else {
stdout.println("向上滚动");
}
// 阻止页面滚动
evt.preventDefault();
});
}
// 自定义菜单实现
function showCustomMenu(x, y) {
var menu = $("#context-menu");
if (!menu) {
menu = Element.create("div", {
id: "context-menu",
class: "context-menu",
html: `
<div class="menu-item" data-action="copy">复制</div>
<div class="menu-item" data-action="paste">粘贴</div>
<div class="menu-separator"></div>
<div class="menu-item" data-action="delete">删除</div>
`
});
document.body.append(menu);
// 绑定菜单项事件
menu.on("click", ".menu-item", function(evt) {
var action = this.attributes["data-action"];
executeMenuAction(action);
hideCustomMenu();
});
}
menu.style.left = x + "px";
menu.style.top = y + "px";
menu.style.display = "block";
// 点击其他地方隐藏菜单
document.on("click.menu", function(evt) {
if (!menu.contains(evt.target)) {
hideCustomMenu();
}
});
}
function hideCustomMenu() {
var menu = $("#context-menu");
if (menu) {
menu.style.display = "none";
document.off("click.menu");
}
}
function executeMenuAction(action) {
switch (action) {
case "copy":
stdout.println("执行复制操作");
break;
case "paste":
stdout.println("执行粘贴操作");
break;
case "delete":
stdout.println("执行删除操作");
break;
}
}
2. 键盘事件
// 键盘事件处理
function setupKeyboardEvents() {
// 全局键盘事件
document.on("keydown", function(evt) {
handleGlobalKeydown(evt);
});
// 输入框键盘事件
$("#text-input").on("keydown keyup keypress", function(evt) {
handleInputKeyboard(evt);
});
}
function handleGlobalKeydown(evt) {
// 快捷键处理
if (evt.ctrlKey) {
switch (evt.keyCode) {
case 83: // Ctrl+S
evt.preventDefault();
saveDocument();
break;
case 79: // Ctrl+O
evt.preventDefault();
openDocument();
break;
case 78: // Ctrl+N
evt.preventDefault();
newDocument();
break;
case 90: // Ctrl+Z
evt.preventDefault();
undo();
break;
case 89: // Ctrl+Y
evt.preventDefault();
redo();
break;
}
}
// 功能键处理
switch (evt.keyCode) {
case 112: // F1
evt.preventDefault();
showHelp();
break;
case 123: // F12
evt.preventDefault();
toggleDevTools();
break;
case 27: // Escape
closeModal();
break;
}
}
function handleInputKeyboard(evt) {
var input = evt.target;
if (evt.type == "keydown") {
// 特殊键处理
switch (evt.keyCode) {
case 13: // Enter
if (evt.ctrlKey) {
// Ctrl+Enter 提交
submitForm();
} else {
// 普通Enter,插入换行(如果是textarea)
if (input.tag == "textarea") {
insertText(input, "\n");
evt.preventDefault();
}
}
break;
case 9: // Tab
if (evt.shiftKey) {
// Shift+Tab 减少缩进
decreaseIndent(input);
} else {
// Tab 增加缩进
increaseIndent(input);
}
evt.preventDefault();
break;
}
}
if (evt.type == "keyup") {
// 输入验证
validateInput(input);
// 自动保存
scheduleAutoSave();
}
}
// 文本操作辅助函数
function insertText(input, text) {
var start = input.selectionStart;
var end = input.selectionEnd;
var value = input.value;
input.value = value.substring(0, start) + text + value.substring(end);
input.selectionStart = input.selectionEnd = start + text.length;
}
function increaseIndent(input) {
var start = input.selectionStart;
var end = input.selectionEnd;
var value = input.value;
var lines = value.split("\n");
// 找到选中的行
var startLine = value.substring(0, start).split("\n").length - 1;
var endLine = value.substring(0, end).split("\n").length - 1;
// 为每行添加缩进
for (var i = startLine; i <= endLine; i++) {
lines[i] = " " + lines[i];
}
input.value = lines.join("\n");
}
function decreaseIndent(input) {
var start = input.selectionStart;
var end = input.selectionEnd;
var value = input.value;
var lines = value.split("\n");
var startLine = value.substring(0, start).split("\n").length - 1;
var endLine = value.substring(0, end).split("\n").length - 1;
// 移除每行的缩进
for (var i = startLine; i <= endLine; i++) {
if (lines[i].startsWith(" ")) {
lines[i] = lines[i].substring(4);
} else if (lines[i].startsWith("\t")) {
lines[i] = lines[i].substring(1);
}
}
input.value = lines.join("\n");
}
3. 表单事件
// 表单事件处理
function setupFormEvents() {
var form = $("#user-form");
// 表单提交事件
form.on("submit", function(evt) {
evt.preventDefault(); // 阻止默认提交
if (validateForm(this)) {
submitFormData(this);
} else {
showValidationErrors();
}
});
// 表单重置事件
form.on("reset", function(evt) {
clearValidationErrors();
resetFormState();
});
// 输入字段事件
form.on("input", "input, textarea, select", function(evt) {
handleFieldInput(this);
});
form.on("change", "input, textarea, select", function(evt) {
handleFieldChange(this);
});
form.on("focus", "input, textarea, select", function(evt) {
handleFieldFocus(this);
});
form.on("blur", "input, textarea, select", function(evt) {
handleFieldBlur(this);
});
}
function handleFieldInput(field) {
// 实时输入处理
clearFieldError(field);
// 字符计数
if (field.hasAttribute("maxlength")) {
updateCharacterCount(field);
}
// 实时验证
if (field.hasAttribute("data-validate-realtime")) {
validateField(field);
}
}
function handleFieldChange(field) {
// 值改变处理
validateField(field);
updateFormState();
// 特殊字段处理
if (field.name == "country") {
updateStateOptions(field.value);
} else if (field.name == "userType") {
toggleUserTypeFields(field.value);
}
}
function handleFieldFocus(field) {
// 获得焦点处理
field.addClass("focused");
showFieldHelp(field);
}
function handleFieldBlur(field) {
// 失去焦点处理
field.removeClass("focused");
hideFieldHelp(field);
validateField(field);
}
// 表单验证
function validateForm(form) {
var isValid = true;
var fields = form.$$(".required");
for (var field of fields) {
if (!validateField(field)) {
isValid = false;
}
}
return isValid;
}
function validateField(field) {
var value = field.value.trim();
var isValid = true;
var errorMessage = "";
// 必填验证
if (field.hasClass("required") && !value) {
isValid = false;
errorMessage = "此字段为必填项";
}
// 类型验证
if (isValid && value) {
var type = field.getAttribute("data-type");
switch (type) {
case "email":
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
isValid = false;
errorMessage = "请输入有效的邮箱地址";
}
break;
case "phone":
if (!/^\d{11}$/.test(value)) {
isValid = false;
errorMessage = "请输入11位手机号码";
}
break;
case "number":
if (isNaN(value)) {
isValid = false;
errorMessage = "请输入有效的数字";
}
break;
}
}
// 长度验证
if (isValid && value) {
var minLength = field.getAttribute("minlength");
var maxLength = field.getAttribute("maxlength");
if (minLength && value.length < parseInt(minLength)) {
isValid = false;
errorMessage = `最少需要${minLength}个字符`;
}
if (maxLength && value.length > parseInt(maxLength)) {
isValid = false;
errorMessage = `最多允许${maxLength}个字符`;
}
}
// 显示验证结果
if (isValid) {
field.removeClass("error");
field.addClass("valid");
clearFieldError(field);
} else {
field.removeClass("valid");
field.addClass("error");
showFieldError(field, errorMessage);
}
return isValid;
}
function showFieldError(field, message) {
var errorElement = field.parent.$("."+field.name+"-error");
if (!errorElement) {
errorElement = Element.create("div", {
class: field.name + "-error field-error",
text: message
});
field.parent.append(errorElement);
} else {
errorElement.innerText = message;
}
}
function clearFieldError(field) {
var errorElement = field.parent.$("."+field.name+"-error");
if (errorElement) {
errorElement.remove();
}
}
4.3 自定义事件
创建和触发自定义事件
1. 基本自定义事件
// 创建自定义事件类
class CustomEvent {
function this(type, data = {}) {
this.type = type;
this.data = data;
this.timestamp = Date.now();
this.defaultPrevented = false;
this.propagationStopped = false;
}
function preventDefault() {
this.defaultPrevented = true;
}
function stopPropagation() {
this.propagationStopped = true;
}
}
// 事件发射器类
class EventEmitter {
function this() {
this._events = {};
}
// 注册事件监听器
function on(eventType, handler) {
if (!this._events[eventType]) {
this._events[eventType] = [];
}
this._events[eventType].push(handler);
return this;
}
// 注册一次性事件监听器
function once(eventType, handler) {
var self = this;
function onceHandler(evt) {
handler.call(this, evt);
self.off(eventType, onceHandler);
}
return this.on(eventType, onceHandler);
}
// 移除事件监听器
function off(eventType, handler) {
if (!this._events[eventType]) return this;
if (!handler) {
// 移除所有监听器
delete this._events[eventType];
} else {
// 移除特定监听器
var index = this._events[eventType].indexOf(handler);
if (index >= 0) {
this._events[eventType].splice(index, 1);
}
}
return this;
}
// 触发事件
function emit(eventType, data) {
if (!this._events[eventType]) return this;
var event = new CustomEvent(eventType, data);
var handlers = this._events[eventType].slice(); // 复制数组
for (var handler of handlers) {
try {
handler.call(this, event);
if (event.propagationStopped) break;
} catch (error) {
stderr.println("事件处理器错误: " + error.message);
}
}
return this;
}
// 获取监听器数量
function listenerCount(eventType) {
return this._events[eventType] ? this._events[eventType].length : 0;
}
// 获取所有事件类型
function eventNames() {
return Object.keys(this._events);
}
}
// 使用示例
var emitter = new EventEmitter();
// 注册事件监听器
emitter.on("user-login", function(evt) {
stdout.println("用户登录: " + evt.data.username);
updateUserInterface(evt.data);
});
emitter.on("user-logout", function(evt) {
stdout.println("用户登出");
clearUserInterface();
});
emitter.on("data-updated", function(evt) {
stdout.println("数据更新: " + evt.data.table);
refreshDataView(evt.data);
});
// 触发事件
emitter.emit("user-login", {
username: "张三",
userId: 123,
role: "admin"
});
emitter.emit("data-updated", {
table: "users",
action: "insert",
recordId: 456
});
2. 应用级事件系统
// 全局应用事件系统
namespace AppEvents {
var _emitter = new EventEmitter();
// 预定义事件类型
const EVENT_TYPES = {
APP_READY: "app-ready",
USER_LOGIN: "user-login",
USER_LOGOUT: "user-logout",
DATA_CHANGED: "data-changed",
SETTINGS_UPDATED: "settings-updated",
ERROR_OCCURRED: "error-occurred",
NOTIFICATION_SHOW: "notification-show",
WINDOW_RESIZE: "window-resize",
THEME_CHANGED: "theme-changed"
};
// 公共接口
function on(eventType, handler) {
return _emitter.on(eventType, handler);
}
function once(eventType, handler) {
return _emitter.once(eventType, handler);
}
function off(eventType, handler) {
return _emitter.off(eventType, handler);
}
function emit(eventType, data) {
stdout.println(`[AppEvents] 触发事件: ${eventType}`);
return _emitter.emit(eventType, data);
}
// 便捷方法
function notifyUserLogin(user) {
return emit(EVENT_TYPES.USER_LOGIN, user);
}
function notifyUserLogout() {
return emit(EVENT_TYPES.USER_LOGOUT);
}
function notifyDataChanged(table, action, data) {
return emit(EVENT_TYPES.DATA_CHANGED, {
table: table,
action: action,
data: data,
timestamp: Date.now()
});
}
function notifyError(error, context) {
return emit(EVENT_TYPES.ERROR_OCCURRED, {
error: error,
context: context,
timestamp: Date.now()
});
}
function showNotification(message, type = "info") {
return emit(EVENT_TYPES.NOTIFICATION_SHOW, {
message: message,
type: type,
timestamp: Date.now()
});
}
// 导出常量和函数
self.EVENT_TYPES = EVENT_TYPES;
self.on = on;
self.once = once;
self.off = off;
self.emit = emit;
self.notifyUserLogin = notifyUserLogin;
self.notifyUserLogout = notifyUserLogout;
self.notifyDataChanged = notifyDataChanged;
self.notifyError = notifyError;
self.showNotification = showNotification;
}
// 使用应用事件系统
function setupApplicationEvents() {
// 监听用户登录事件
AppEvents.on(AppEvents.EVENT_TYPES.USER_LOGIN, function(evt) {
var user = evt.data;
stdout.println(`欢迎 ${user.name}!`);
// 更新UI
updateUserMenu(user);
loadUserPreferences(user.id);
// 记录日志
logUserActivity("login", user.id);
});
// 监听数据变化事件
AppEvents.on(AppEvents.EVENT_TYPES.DATA_CHANGED, function(evt) {
var data = evt.data;
stdout.println(`数据表 ${data.table} 发生 ${data.action} 操作`);
// 刷新相关视图
refreshViews(data.table);
// 标记数据为脏状态
markDataDirty(data.table);
});
// 监听错误事件
AppEvents.on(AppEvents.EVENT_TYPES.ERROR_OCCURRED, function(evt) {
var error = evt.data.error;
var context = evt.data.context;
stderr.println(`错误发生在 ${context}: ${error.message}`);
// 显示错误通知
showErrorDialog(error.message);
// 发送错误报告
sendErrorReport(error, context);
});
// 监听通知事件
AppEvents.on(AppEvents.EVENT_TYPES.NOTIFICATION_SHOW, function(evt) {
var notification = evt.data;
showToast(notification.message, notification.type);
});
}
事件总线模式
1. 模块间通信
// 事件总线实现
class EventBus {
function this() {
this._channels = {};
this._middlewares = [];
}
// 添加中间件
function use(middleware) {
this._middlewares.push(middleware);
return this;
}
// 订阅频道
function subscribe(channel, handler, priority = 0) {
if (!this._channels[channel]) {
this._channels[channel] = [];
}
this._channels[channel].push({
handler: handler,
priority: priority
});
// 按优先级排序
this._channels[channel].sort((a, b) => b.priority - a.priority);
return this;
}
// 取消订阅
function unsubscribe(channel, handler) {
if (!this._channels[channel]) return this;
this._channels[channel] = this._channels[channel].filter(
item => item.handler !== handler
);
return this;
}
// 发布消息
function publish(channel, message, options = {}) {
if (!this._channels[channel]) return this;
var event = {
channel: channel,
message: message,
options: options,
timestamp: Date.now(),
stopped: false
};
// 执行中间件
for (var middleware of this._middlewares) {
try {
middleware(event);
if (event.stopped) return this;
} catch (error) {
stderr.println("中间件错误: " + error.message);
}
}
// 执行处理器
var handlers = this._channels[channel].slice();
for (var item of handlers) {
try {
item.handler(event.message, event);
if (event.stopped) break;
} catch (error) {
stderr.println(`频道 ${channel} 处理器错误: ${error.message}`);
}
}
return this;
}
// 清空频道
function clear(channel) {
if (channel) {
delete this._channels[channel];
} else {
this._channels = {};
}
return this;
}
// 获取频道信息
function getChannels() {
return Object.keys(this._channels);
}
function getSubscriberCount(channel) {
return this._channels[channel] ? this._channels[channel].length : 0;
}
}
// 全局事件总线
var GlobalEventBus = new EventBus();
// 添加日志中间件
GlobalEventBus.use(function(event) {
stdout.println(`[EventBus] ${event.channel}: ${JSON.stringify(event.message)}`);
});
// 添加权限检查中间件
GlobalEventBus.use(function(event) {
if (event.options.requireAuth && !isUserAuthenticated()) {
stderr.println("未授权的事件访问: " + event.channel);
event.stopped = true;
}
});
// 模块A:用户管理
namespace UserModule {
function init() {
// 订阅用户相关事件
GlobalEventBus.subscribe("user.create", handleUserCreate);
GlobalEventBus.subscribe("user.update", handleUserUpdate);
GlobalEventBus.subscribe("user.delete", handleUserDelete);
}
function handleUserCreate(userData) {
stdout.println("创建用户: " + userData.name);
// 创建用户逻辑
var user = createUser(userData);
// 通知其他模块
GlobalEventBus.publish("user.created", user);
}
function handleUserUpdate(userData) {
stdout.println("更新用户: " + userData.id);
var user = updateUser(userData);
GlobalEventBus.publish("user.updated", user);
}
function handleUserDelete(userId) {
stdout.println("删除用户: " + userId);
deleteUser(userId);
GlobalEventBus.publish("user.deleted", { id: userId });
}
self.init = init;
}
// 模块B:通知系统
namespace NotificationModule {
function init() {
// 订阅用户事件,发送通知
GlobalEventBus.subscribe("user.created", function(user) {
sendWelcomeNotification(user);
});
GlobalEventBus.subscribe("user.updated", function(user) {
sendUpdateNotification(user);
});
// 订阅通知事件
GlobalEventBus.subscribe("notification.send", handleSendNotification);
}
function handleSendNotification(notification) {
stdout.println(`发送通知: ${notification.message}`);
// 发送通知逻辑
var result = sendNotification(notification);
// 通知发送结果
GlobalEventBus.publish("notification.sent", {
notification: notification,
result: result
});
}
function sendWelcomeNotification(user) {
GlobalEventBus.publish("notification.send", {
to: user.email,
subject: "欢迎注册",
message: `欢迎 ${user.name} 加入我们!`
});
}
function sendUpdateNotification(user) {
GlobalEventBus.publish("notification.send", {
to: user.email,
subject: "账户更新",
message: "您的账户信息已更新"
});
}
self.init = init;
}
// 初始化模块
UserModule.init();
NotificationModule.init();
// 测试事件总线
GlobalEventBus.publish("user.create", {
name: "张三",
email: "zhangsan@example.com",
role: "user"
});
4.4 拖拽交互
拖拽事件处理
1. 基本拖拽实现
// 拖拽管理器
class DragManager {
function this() {
this.isDragging = false;
this.dragElement = null;
this.dragData = null;
this.dropZones = [];
this.dragOffset = { x: 0, y: 0 };
this.setupGlobalEvents();
}
function setupGlobalEvents() {
var self = this;
document.on("mousemove", function(evt) {
if (self.isDragging) {
self.handleDragMove(evt);
}
});
document.on("mouseup", function(evt) {
if (self.isDragging) {
self.handleDragEnd(evt);
}
});
}
// 使元素可拖拽
function makeDraggable(element, options = {}) {
var self = this;
element.on("mousedown", function(evt) {
if (evt.button === 0) { // 左键
self.startDrag(this, evt, options);
}
});
element.addClass("draggable");
element.style.cursor = "grab";
}
// 注册放置区域
function registerDropZone(element, options = {}) {
this.dropZones.push({
element: element,
options: options
});
element.addClass("drop-zone");
}
// 开始拖拽
function startDrag(element, evt, options) {
this.isDragging = true;
this.dragElement = element;
this.dragData = options.data || {};
// 计算拖拽偏移
var rect = element.getBoundingClientRect();
this.dragOffset.x = evt.clientX - rect.left;
this.dragOffset.y = evt.clientY - rect.top;
// 创建拖拽代理
this.createDragProxy(element, evt);
// 添加拖拽样式
element.addClass("dragging");
document.body.addClass("drag-active");
// 触发拖拽开始事件
this.fireDragEvent("dragstart", element, evt);
evt.preventDefault();
}
// 创建拖拽代理
function createDragProxy(element, evt) {
this.dragProxy = element.cloneNode(true);
this.dragProxy.addClass("drag-proxy");
this.dragProxy.style.position = "absolute";
this.dragProxy.style.pointerEvents = "none";
this.dragProxy.style.zIndex = "9999";
this.dragProxy.style.opacity = "0.8";
document.body.append(this.dragProxy);
this.updateProxyPosition(evt);
}
// 更新代理位置
function updateProxyPosition(evt) {
if (this.dragProxy) {
this.dragProxy.style.left = (evt.clientX - this.dragOffset.x) + "px";
this.dragProxy.style.top = (evt.clientY - this.dragOffset.y) + "px";
}
}
// 处理拖拽移动
function handleDragMove(evt) {
this.updateProxyPosition(evt);
// 检查放置区域
var dropZone = this.getDropZoneAt(evt.clientX, evt.clientY);
this.updateDropZoneHighlight(dropZone);
// 触发拖拽移动事件
this.fireDragEvent("dragmove", this.dragElement, evt, dropZone);
}
// 处理拖拽结束
function handleDragEnd(evt) {
var dropZone = this.getDropZoneAt(evt.clientX, evt.clientY);
if (dropZone && this.canDrop(dropZone, this.dragData)) {
this.handleDrop(dropZone, evt);
} else {
this.handleDragCancel(evt);
}
this.cleanup();
}
// 处理放置
function handleDrop(dropZone, evt) {
var dropData = {
dragElement: this.dragElement,
dragData: this.dragData,
dropZone: dropZone.element,
position: { x: evt.clientX, y: evt.clientY }
};
// 触发放置事件
this.fireDragEvent("drop", dropZone.element, evt, dropData);
// 执行放置回调
if (dropZone.options.onDrop) {
dropZone.options.onDrop(dropData);
}
}
// 处理拖拽取消
function handleDragCancel(evt) {
this.fireDragEvent("dragcancel", this.dragElement, evt);
}
// 清理拖拽状态
function cleanup() {
this.isDragging = false;
if (this.dragElement) {
this.dragElement.removeClass("dragging");
this.dragElement = null;
}
if (this.dragProxy) {
this.dragProxy.remove();
this.dragProxy = null;
}
document.body.removeClass("drag-active");
this.clearDropZoneHighlights();
this.dragData = null;
}
// 获取指定位置的放置区域
function getDropZoneAt(x, y) {
for (var dropZone of this.dropZones) {
var rect = dropZone.element.getBoundingClientRect();
if (x >= rect.left && x <= rect.right &&
y >= rect.top && y <= rect.bottom) {
return dropZone;
}
}
return null;
}
// 检查是否可以放置
function canDrop(dropZone, dragData) {
if (dropZone.options.accept) {
if (typeof dropZone.options.accept === "function") {
return dropZone.options.accept(dragData);
} else {
return dropZone.options.accept.includes(dragData.type);
}
}
return true;
}
// 更新放置区域高亮
function updateDropZoneHighlight(activeDropZone) {
for (var dropZone of this.dropZones) {
if (dropZone === activeDropZone) {
dropZone.element.addClass("drop-zone-active");
} else {
dropZone.element.removeClass("drop-zone-active");
}
}
}
// 清除放置区域高亮
function clearDropZoneHighlights() {
for (var dropZone of this.dropZones) {
dropZone.element.removeClass("drop-zone-active");
}
}
// 触发拖拽事件
function fireDragEvent(type, element, originalEvent, data) {
var event = new CustomEvent(type, {
dragElement: this.dragElement,
dragData: this.dragData,
originalEvent: originalEvent,
data: data
});
element.dispatchEvent(event);
}
}
// 创建全局拖拽管理器
var dragManager = new DragManager();
// 使用示例
function setupDragAndDrop() {
// 使卡片可拖拽
$$("#card-container .card").forEach(function(card) {
dragManager.makeDraggable(card, {
data: {
type: "card",
id: card.getAttribute("data-id"),
content: card.innerText
}
});
});
// 注册放置区域
$$("#drop-zones .zone").forEach(function(zone) {
dragManager.registerDropZone(zone, {
accept: ["card"],
onDrop: function(dropData) {
handleCardDrop(dropData);
}
});
});
// 监听拖拽事件
document.on("dragstart", function(evt) {
stdout.println("开始拖拽: " + evt.data.dragData.id);
});
document.on("drop", function(evt) {
stdout.println("放置完成: " + evt.data.dragData.id);
});
}
function handleCardDrop(dropData) {
var card = dropData.dragElement;
var zone = dropData.dropZone;
// 移动卡片到新区域
zone.append(card);
// 更新数据
updateCardPosition(dropData.dragData.id, zone.getAttribute("data-zone"));
// 显示成功消息
showMessage("卡片已移动到 " + zone.getAttribute("data-name"));
}
4.5 本章总结
在本章中,我们深入学习了Sciter的事件处理与交互机制:
事件系统
- DOM事件的绑定和处理
- 事件流程和传播机制
- 常用事件类型的处理方法
自定义事件
- 自定义事件的创建和触发
- 事件发射器模式
- 应用级事件系统设计
高级交互
- 拖拽功能的实现
- 事件总线模式
- 模块间通信机制
实践技巧
- 事件处理的最佳实践
- 性能优化技巧
- 错误处理和调试方法
4.6 练习题
基础练习
- 创建一个响应式的按钮组件,支持多种交互状态
- 实现一个简单的表单验证系统
- 制作一个可拖拽排序的列表组件
- 设计一个键盘快捷键系统
进阶练习
- 实现一个复杂的拖拽看板系统
- 创建一个事件驱动的状态管理系统
- 设计一个可扩展的插件事件系统
- 实现一个手势识别系统
挑战练习
- 创建一个完整的图形编辑器交互系统
- 实现一个多点触控支持的交互界面
- 设计一个实时协作的事件同步系统
- 制作一个游戏级别的输入处理系统
下一章预告: 在第5章中,我们将学习Sciter的窗口管理与布局系统,包括窗口创建、管理、布局算法,以及如何创建复杂的多窗口应用程序。