jQuery事件处理详解
事件处理概述
事件处理是Web开发的核心部分,它允许网页对用户的交互做出响应。jQuery简化了事件处理,提供了跨浏览器兼容的解决方案。
鼠标事件
click - 点击事件
dblclick - 双击事件
mouseenter - 鼠标进入
mouseleave - 鼠标离开
mousemove - 鼠标移动
键盘事件
keydown - 键按下
keyup - 键释放
keypress - 键按下(字符键)
表单事件
focus - 获得焦点
blur - 失去焦点
change - 值改变
submit - 表单提交
窗口事件
load - 加载完成
resize - 调整大小
scroll - 滚动事件
重要概念
事件绑定: 将事件处理函数附加到元素
事件对象: 事件发生时创建的包含事件信息的对象
事件冒泡: 事件从目标元素向上传播到文档根元素
事件委托: 将事件处理程序附加到父元素,处理子元素的事件
事件命名空间: 为事件添加命名空间以便管理
事件绑定方法
jQuery提供了多种事件绑定方法,每种方法都有其适用场景。
on() 方法
jQuery 1.7+ 推荐的事件绑定方法,功能最强大,支持所有事件类型。
// 基本语法
$(selector).on(events[, selector][, data], handler)
// 绑定单个事件
$(selector).on("click", function() {})
// 绑定多个事件
$(selector).on("click mouseenter", function() {})
// 带事件委托
$(parent).on("click", childSelector, function() {})
// 传递数据
$(selector).on("click", {key: value}, function(event) {})
示例:
// 绑定点击事件
$("#button").on("click", function() {
alert("按钮被点击了!");
});
// 绑定多个事件
$("#element").on("mouseenter mouseleave", function(event) {
if (event.type === "mouseenter") {
$(this).css("background-color", "yellow");
} else {
$(this).css("background-color", "white");
}
});
// 事件委托(处理动态添加的元素)
$("#list").on("click", "li", function() {
alert("列表项被点击: " + $(this).text());
});
// 传递数据给事件处理函数
$("#button").on("click", {name: "张三", age: 25}, function(event) {
console.log("数据:", event.data); // {name: "张三", age: 25}
});
// 使用命名空间
$("#element").on("click.myNamespace", function() {
console.log("带命名空间的点击事件");
});
// 一次性事件
$("#button").one("click", function() {
alert("这个按钮只能点击一次!");
});
on() 方法的优势
统一的事件绑定API
支持事件委托
支持事件命名空间
可以传递数据给处理函数
支持一次性事件(通过one()方法)
off() 方法
移除通过 on() 方法绑定的事件处理程序。
// 移除所有事件处理程序
$(selector).off()
// 移除特定类型的事件
$(selector).off("click")
// 移除特定处理函数
$(selector).off("click", handler)
// 移除特定命名空间的事件
$(selector).off(".myNamespace")
// 移除事件委托
$(parent).off("click", childSelector)
示例:
// 定义事件处理函数
function handleClick() {
console.log("按钮被点击");
}
// 绑定事件
$("#button").on("click", handleClick);
// 移除事件
$("#button").off("click", handleClick);
// 绑定多个事件
$("#element").on("click mouseenter", function() {
console.log("事件触发");
});
// 移除所有click事件
$("#element").off("click");
// 移除所有事件
$("#element").off();
// 使用命名空间
$("#element").on("click.myNamespace", function() {
console.log("命名空间事件");
});
// 移除特定命名空间的事件
$("#element").off(".myNamespace");
// 事件委托的移除
$("#list").on("click", "li", function() {
console.log("列表项点击");
});
// 移除事件委托
$("#list").off("click", "li");
// 移除特定处理函数的事件委托
function handleListItemClick() {
console.log("列表项点击");
}
$("#list").on("click", "li", handleListItemClick);
$("#list").off("click", "li", handleListItemClick);
trigger() 方法
触发绑定到元素的事件处理程序。
// 触发事件
$(selector).trigger(eventType[, extraParameters])
// 触发自定义事件
$(selector).trigger("customEvent")
// 传递额外参数
$(selector).trigger("click", [param1, param2])
示例:
// 绑定点击事件
$("#button").on("click", function(event, param1, param2) {
console.log("按钮被点击", param1, param2);
});
// 触发点击事件
$("#button").trigger("click");
// 触发事件并传递参数
$("#button").trigger("click", ["参数1", "参数2"]);
// 触发自定义事件
$("#element").on("myCustomEvent", function() {
console.log("自定义事件被触发");
});
$("#element").trigger("myCustomEvent");
// 触发多个事件
$("#element").on("event1 event2", function(event) {
console.log(event.type + " 被触发");
});
$("#element").trigger("event1 event2");
// 触发表单提交事件
$("#form").trigger("submit");
// 触发带命名空间的事件
$("#element").on("click.myNamespace", function() {
console.log("命名空间事件");
});
$("#element").trigger("click.myNamespace");
快捷方法
jQuery为常用事件提供了快捷方法,语法更简洁。
// 点击事件
$(selector).click(handler)
// 鼠标进入事件
$(selector).mouseenter(handler)
// 键盘按下事件
$(selector).keydown(handler)
// 聚焦事件
$(selector).focus(handler)
// 加载事件
$(window).load(handler)
示例:
// 快捷方法绑定事件
$("#button").click(function() {
alert("按钮被点击");
});
$("#input").focus(function() {
$(this).css("border-color", "blue");
});
$("#input").blur(function() {
$(this).css("border-color", "#ccc");
});
$("#element").mouseenter(function() {
$(this).css("background-color", "yellow");
}).mouseleave(function() {
$(this).css("background-color", "white");
});
// 快捷方法触发事件
$("#button").click(); // 触发点击事件
$("#input").focus(); // 触发聚焦事件
// 快捷方法移除事件
$("#button").off("click"); // 只能这样移除,没有.unclick()方法
// 注意:快捷方法内部也是调用on()方法
// 以下两行代码是等价的:
$("#button").click(function() {});
$("#button").on("click", function() {});
事件绑定方法对比
方法
描述
版本
推荐度
on()
统一的事件绑定方法,功能最全
1.7+
★★★★★
off()
移除通过on()绑定的事件
1.7+
★★★★★
bind()
旧版事件绑定方法(已过时)
1.0-3.0
★☆☆☆☆
unbind()
旧版事件移除方法(已过时)
1.0-3.0
★☆☆☆☆
delegate()
旧版事件委托方法(已过时)
1.4.2-3.0
★☆☆☆☆
undelegate()
旧版事件委托移除(已过时)
1.4.2-3.0
★☆☆☆☆
快捷方法
click(), mouseenter()等
1.0+
★★★★☆
鼠标事件
鼠标事件在用户使用鼠标与元素交互时触发。
click
点击事件(按下并释放鼠标按钮)
$("#element").click(function() {
console.log("元素被点击");
});
dblclick
双击事件
$("#element").dblclick(function() {
console.log("元素被双击");
});
mousedown
鼠标按钮按下
$("#element").mousedown(function(event) {
console.log("按钮按下:", event.which);
});
mouseup
鼠标按钮释放
$("#element").mouseup(function() {
console.log("按钮释放");
});
mouseenter
鼠标进入元素区域
$("#element").mouseenter(function() {
$(this).addClass("hover");
});
mouseleave
鼠标离开元素区域
$("#element").mouseleave(function() {
$(this).removeClass("hover");
});
mouseover
鼠标移动到元素上(会冒泡)
$("#element").mouseover(function() {
console.log("mouseover");
});
mouseout
鼠标移出元素(会冒泡)
$("#element").mouseout(function() {
console.log("mouseout");
});
mousemove
鼠标在元素上移动
$("#element").mousemove(function(event) {
console.log("位置:", event.pageX, event.pageY);
});
contextmenu
右键菜单事件
$("#element").contextmenu(function(event) {
event.preventDefault(); // 阻止默认右键菜单
showCustomMenu();
});
鼠标事件综合示例
展示鼠标事件的常见用法和事件对象属性。
// 鼠标事件综合示例
$("#demo-element")
// 点击事件
.on("click", function(event) {
console.log("点击事件");
console.log("鼠标按钮:", event.which); // 1=左键, 2=中键, 3=右键
console.log("坐标:", event.pageX, event.pageY);
console.log("目标元素:", event.target.tagName);
})
// 双击事件
.on("dblclick", function() {
console.log("双击事件");
$(this).toggleClass("highlight");
})
// 鼠标进入/离开
.on("mouseenter mouseleave", function(event) {
if (event.type === "mouseenter") {
$(this).css("border-color", "#3498db");
} else {
$(this).css("border-color", "#ddd");
}
})
// 鼠标移动
.on("mousemove", function(event) {
$("#coords").text(event.pageX + ", " + event.pageY);
})
// 鼠标按下/释放
.on("mousedown mouseup", function(event) {
if (event.type === "mousedown") {
$(this).css("background-color", "#e74c3c");
} else {
$(this).css("background-color", "#3498db");
}
console.log("按钮:", event.which);
})
// 右键菜单
.on("contextmenu", function(event) {
event.preventDefault();
alert("自定义右键菜单");
return false;
});
// 获取鼠标位置
$(document).on("mousemove", function(event) {
// event.pageX/Y: 相对于文档的坐标
// event.clientX/Y: 相对于视口的坐标
// event.screenX/Y: 相对于屏幕的坐标
console.log("文档坐标:", event.pageX, event.pageY);
console.log("视口坐标:", event.clientX, event.clientY);
console.log("屏幕坐标:", event.screenX, event.screenY);
});
// 鼠标事件对象属性示例
$("#element").on("click", function(event) {
console.log("事件类型:", event.type); // "click"
console.log("时间戳:", event.timeStamp); // 事件发生的时间戳
console.log("目标元素:", event.target); // 触发事件的元素
console.log("当前元素:", event.currentTarget); // 绑定事件处理程序的元素
console.log("是否按下了Shift键:", event.shiftKey);
console.log("是否按下了Ctrl键:", event.ctrlKey);
console.log("是否按下了Alt键:", event.altKey);
console.log("是否按下了Meta键:", event.metaKey);
console.log("鼠标按钮:", event.button); // 0=左键, 1=中键, 2=右键
console.log("相关元素:", event.relatedTarget); // 对于mouseenter/mouseleave事件
});
鼠标事件演示:
在此区域移动、点击鼠标
事件日志:
清空日志
mouseenter/mouseleave vs mouseover/mouseout
特性
mouseenter/mouseleave
mouseover/mouseout
事件冒泡
不会冒泡
会冒泡
触发频率
进入/离开元素时触发一次
进入/离开元素及其子元素时都会触发
推荐场景
鼠标悬停效果
需要事件冒泡的场景
jQuery版本
1.0+
1.0+
示例
$("#parent").mouseenter(function() {
// 只在鼠标进入父元素时触发
});
$("#parent").mouseover(function() {
// 鼠标进入父元素或子元素都会触发
});
键盘事件
键盘事件在用户按下或释放键盘按键时触发。
keydown
键按下时触发(任何键)
$("#input").keydown(function(event) {
console.log("键按下:", event.key);
});
keyup
键释放时触发
$("#input").keyup(function(event) {
console.log("键释放:", event.key);
});
keypress
字符键按下时触发
$("#input").keypress(function(event) {
console.log("字符键:", event.which);
});
键盘事件综合示例
展示键盘事件的常见用法和事件对象属性。
// 键盘事件综合示例
$("#text-input").on({
// keydown - 键按下
keydown: function(event) {
console.log("keydown - 键码:", event.keyCode);
console.log("键:", event.key);
console.log("是否Ctrl键:", event.ctrlKey);
// 检测特定按键
if (event.keyCode === 13) { // Enter键
console.log("Enter键被按下");
event.preventDefault(); // 阻止默认行为
}
if (event.keyCode === 27) { // ESC键
console.log("ESC键被按下");
$(this).blur();
}
// 组合键检测
if (event.ctrlKey && event.keyCode === 83) { // Ctrl+S
event.preventDefault();
saveContent();
}
},
// keyup - 键释放
keyup: function(event) {
console.log("keyup - 键:", event.key);
// 实时显示输入长度
var length = $(this).val().length;
$("#char-count").text(length);
},
// keypress - 字符键按下
keypress: function(event) {
console.log("keypress - 字符码:", event.which);
// 限制只能输入数字
if (!/[0-9]/.test(String.fromCharCode(event.which))) {
event.preventDefault();
}
}
});
// 全局键盘事件
$(document).on("keydown", function(event) {
// 快捷键支持
switch(event.keyCode) {
case 37: // 左箭头
navigateLeft();
break;
case 39: // 右箭头
navigateRight();
break;
case 38: // 上箭头
navigateUp();
break;
case 40: // 下箭头
navigateDown();
break;
}
});
// 键盘事件对象属性
$("#input").on("keydown", function(event) {
console.log("事件类型:", event.type); // "keydown"
console.log("键码:", event.keyCode); // 按键的键码
console.log("字符码:", event.which); // 字符码(已弃用,但jQuery标准化)
console.log("键:", event.key); // 按键的字符串表示(推荐)
console.log("代码:", event.code); // 物理按键代码
console.log("是否重复:", event.repeat); // 是否按住不放
console.log("是否Shift键:", event.shiftKey);
console.log("是否Ctrl键:", event.ctrlKey);
console.log("是否Alt键:", event.altKey);
console.log("是否Meta键:", event.metaKey);
console.log("是否大写锁定:", event.capsLock);
console.log("是否数字锁定:", event.numLock);
// 阻止默认行为
if (event.key === "Escape") {
event.preventDefault();
$(this).val("");
}
});
// 常用键码
var keyCodes = {
ENTER: 13,
ESC: 27,
SPACE: 32,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
TAB: 9,
BACKSPACE: 8,
DELETE: 46,
HOME: 36,
END: 35,
PAGE_UP: 33,
PAGE_DOWN: 34,
F1: 112,
F12: 123
};
// 检测组合键
$(document).on("keydown", function(event) {
// Ctrl + C
if (event.ctrlKey && event.key === "c") {
console.log("复制");
}
// Ctrl + V
if (event.ctrlKey && event.key === "v") {
console.log("粘贴");
}
// Ctrl + Z
if (event.ctrlKey && event.key === "z") {
console.log("撤销");
event.preventDefault();
}
// Alt + 键
if (event.altKey && event.key === "s") {
console.log("Alt+S 保存");
event.preventDefault();
}
});
键盘事件演示:
字符数: 0
最后按键: 无
键码: -
事件日志:
清空日志
键盘事件对比
事件
触发时机
支持的键
推荐用途
keydown
键按下时
所有键
检测功能键、快捷键
keyup
键释放时
所有键
完成输入、检测键释放
keypress
字符键按下时
字符键(a-z, 0-9等)
字符输入处理(已弃用)
表单事件
表单事件在表单元素与用户交互时触发。
focus
元素获得焦点时触发
$("input").focus(function() {
$(this).addClass("focused");
});
blur
元素失去焦点时触发
$("input").blur(function() {
validateField(this);
});
change
元素值改变并失去焦点时触发
$("select").change(function() {
updateSelection($(this).val());
});
input
元素值改变时立即触发
$("textarea").on("input", function() {
updateCharCount($(this).val());
});
submit
表单提交时触发
$("form").submit(function(event) {
if (!validateForm()) {
event.preventDefault();
}
});
select
文本被选择时触发
$("input[type='text']").select(function() {
console.log("文本被选择");
});
表单事件综合示例
展示表单事件的常见用法和表单验证。
// 表单事件综合示例
$("#myForm").on({
// 提交事件
submit: function(event) {
event.preventDefault(); // 阻止默认提交
if (validateForm()) {
// 验证通过,手动提交
console.log("表单提交:", $(this).serialize());
// Ajax提交
$.ajax({
url: $(this).attr("action"),
method: $(this).attr("method"),
data: $(this).serialize(),
success: function(response) {
console.log("提交成功:", response);
}
});
} else {
console.log("表单验证失败");
}
},
// 重置事件
reset: function() {
console.log("表单被重置");
$(".error-message").hide();
}
});
// 输入框事件
$("#username")
// 获得焦点
.focus(function() {
$(this).css("border-color", "#3498db");
$("#username-help").show();
})
// 失去焦点
.blur(function() {
$(this).css("border-color", "#ddd");
$("#username-help").hide();
// 验证用户名
validateUsername($(this).val());
})
// 输入时实时验证
.on("input", function() {
var value = $(this).val();
$("#username-length").text(value.length);
if (value.length > 0 && value.length < 3) {
showError($(this), "用户名至少3个字符");
} else {
clearError($(this));
}
})
// 值改变(失去焦点后)
.change(function() {
console.log("用户名改变为:", $(this).val());
});
// 下拉框事件
$("#country")
.change(function() {
var country = $(this).val();
console.log("选择的国家:", country);
// 动态加载城市
loadCities(country);
});
// 复选框事件
$("input[type='checkbox']")
.change(function() {
var isChecked = $(this).prop("checked");
console.log("复选框状态:", isChecked);
// 更新选中计数
var checkedCount = $("input[type='checkbox']:checked").length;
$("#selected-count").text(checkedCount);
});
// 单选框事件
$("input[type='radio']")
.change(function() {
var value = $(this).val();
console.log("选择的值:", value);
});
// 表单验证函数
function validateForm() {
var isValid = true;
// 验证用户名
if ($("#username").val().trim() === "") {
showError($("#username"), "用户名不能为空");
isValid = false;
}
// 验证邮箱
var email = $("#email").val();
var emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
showError($("#email"), "邮箱格式不正确");
isValid = false;
}
// 验证密码
var password = $("#password").val();
if (password.length < 6) {
showError($("#password"), "密码至少6个字符");
isValid = false;
}
return isValid;
}
function showError($element, message) {
$element.css("border-color", "#e74c3c");
$element.next(".error-message").remove();
$element.after('' + message + '');
}
function clearError($element) {
$element.css("border-color", "#ddd");
$element.next(".error-message").remove();
}
// 获取表单数据
$("#submit-btn").click(function() {
// 序列化表单数据
var formData = $("#myForm").serialize();
console.log("表单数据:", formData);
// 序列化为数组
var formArray = $("#myForm").serializeArray();
console.log("表单数组:", formArray);
// 获取特定字段值
var username = $("#username").val();
var email = $("#email").val();
var newsletter = $("#newsletter").prop("checked");
// 获取复选框值(多选)
var hobbies = [];
$("input[name='hobbies']:checked").each(function() {
hobbies.push($(this).val());
});
});
表单事件演示:
用户名:
用户名长度: 0
邮箱:
国家:
请选择
中国
美国
日本
兴趣:
阅读
运动
音乐
提交
重置
事件日志:
清空日志
窗口事件
窗口事件在浏览器窗口状态改变时触发。
load
页面完全加载后触发
$(window).load(function() {
console.log("页面完全加载");
});
resize
窗口大小改变时触发
$(window).resize(function() {
console.log("窗口大小:", $(this).width());
});
scroll
窗口滚动时触发
$(window).scroll(function() {
console.log("滚动位置:", $(this).scrollTop());
});
unload
页面卸载时触发(已弃用)
$(window).on("unload", function() {
// 清理工作
});
beforeunload
页面卸载前触发
$(window).on("beforeunload", function() {
return "确定离开页面吗?";
});
窗口事件综合示例
展示窗口事件的常见用法和响应式设计。
// 窗口事件综合示例
$(window)
// 页面加载完成
.on("load", function() {
console.log("页面加载完成");
// 初始化操作
initPage();
// 隐藏加载动画
$("#loading").fadeOut();
})
// 窗口大小改变
.on("resize", function() {
var width = $(this).width();
var height = $(this).height();
console.log("窗口尺寸:", width + "x" + height);
// 响应式设计
if (width < 768) {
$("body").addClass("mobile").removeClass("desktop");
} else {
$("body").addClass("desktop").removeClass("mobile");
}
// 调整布局
adjustLayout(width);
})
// 窗口滚动
.on("scroll", function() {
var scrollTop = $(this).scrollTop();
var windowHeight = $(this).height();
var documentHeight = $(document).height();
console.log("滚动位置:", scrollTop);
// 滚动到顶部按钮
if (scrollTop > 300) {
$("#back-to-top").fadeIn();
} else {
$("#back-to-top").fadeOut();
}
// 无限滚动
if (scrollTop + windowHeight >= documentHeight - 100) {
loadMoreContent();
}
// 视差滚动效果
$(".parallax").css("transform", "translateY(" + scrollTop * 0.5 + "px)");
// 固定导航栏
if (scrollTop > $("#header").height()) {
$("#navbar").addClass("fixed");
} else {
$("#navbar").removeClass("fixed");
}
});
// 页面加载状态
$(document).ready(function() {
console.log("DOM准备就绪");
// DOM操作可以在这里进行
$("#content").html("页面已加载");
});
// 注意:$(document).ready() vs $(window).load()
// ready: DOM加载完成,图片等资源可能还没加载
// load: 所有资源(图片、样式等)都加载完成
// 页面卸载前
$(window).on("beforeunload", function(event) {
// 如果有未保存的数据
if (hasUnsavedChanges()) {
var message = "您有未保存的更改,确定要离开吗?";
event.returnValue = message; // 标准方法
return message; // 兼容旧浏览器
}
});
// 页面卸载
$(window).on("unload", function() {
// 清理工作(注意:限制很多)
// 可以发送一个同步的Ajax请求
// 但更推荐使用visibilitychange事件
});
// 页面可见性API
$(document).on("visibilitychange", function() {
if (document.hidden) {
console.log("页面被隐藏");
// 暂停视频、动画等
} else {
console.log("页面可见");
// 恢复视频、动画等
}
});
// 获取窗口信息
function getWindowInfo() {
return {
width: $(window).width(), // 视口宽度
height: $(window).height(), // 视口高度
scrollTop: $(window).scrollTop(), // 垂直滚动位置
scrollLeft: $(window).scrollLeft(), // 水平滚动位置
screenWidth: screen.width, // 屏幕宽度
screenHeight: screen.height, // 屏幕高度
devicePixelRatio: window.devicePixelRatio // 设备像素比
};
}
// 响应式断点检测
function checkBreakpoint() {
var width = $(window).width();
if (width >= 1200) {
return "xl"; // 特大屏幕
} else if (width >= 992) {
return "lg"; // 大屏幕
} else if (width >= 768) {
return "md"; // 中等屏幕
} else if (width >= 576) {
return "sm"; // 小屏幕
} else {
return "xs"; // 超小屏幕
}
}
// 监听断点变化
var currentBreakpoint = checkBreakpoint();
$(window).on("resize", function() {
var newBreakpoint = checkBreakpoint();
if (newBreakpoint !== currentBreakpoint) {
console.log("断点变化:", currentBreakpoint, "->", newBreakpoint);
currentBreakpoint = newBreakpoint;
// 执行断点相关的操作
onBreakpointChange(newBreakpoint);
}
});
窗口事件演示:
窗口信息
宽度: -px
高度: -px
滚动位置: -px
屏幕尺寸: -
断点: -
滚动此区域查看滚动事件
加载中...
继续滚动...
事件日志:
清空日志
触摸事件
触摸事件在触摸屏设备上与元素交互时触发。
touchstart
触摸开始时触发
$("#element").on("touchstart", function(event) {
console.log("触摸开始");
});
touchmove
触摸移动时触发
$("#element").on("touchmove", function(event) {
event.preventDefault();
});
touchend
触摸结束时触发
$("#element").on("touchend", function(event) {
console.log("触摸结束");
});
touchcancel
触摸被取消时触发
$("#element").on("touchcancel", function(event) {
console.log("触摸取消");
});
触摸事件综合示例
展示触摸事件的常见用法和移动端手势识别。
// 触摸事件综合示例
var touchStartX = 0;
var touchStartY = 0;
var touchEndX = 0;
var touchEndY = 0;
$("#touch-element")
// 触摸开始
.on("touchstart", function(event) {
event.preventDefault();
var touch = event.originalEvent.touches[0];
touchStartX = touch.pageX;
touchStartY = touch.pageY;
console.log("触摸开始:", touchStartX, touchStartY);
$(this).addClass("touching");
// 记录触摸时间
$(this).data("touchStartTime", Date.now());
})
// 触摸移动
.on("touchmove", function(event) {
event.preventDefault();
var touch = event.originalEvent.touches[0];
var currentX = touch.pageX;
var currentY = touch.pageY;
console.log("触摸移动:", currentX, currentY);
// 计算移动距离
var deltaX = currentX - touchStartX;
var deltaY = currentY - touchStartY;
// 拖拽效果
$(this).css({
transform: "translate(" + deltaX + "px, " + deltaY + "px)"
});
})
// 触摸结束
.on("touchend", function(event) {
event.preventDefault();
var touch = event.originalEvent.changedTouches[0];
touchEndX = touch.pageX;
touchEndY = touch.pageY;
console.log("触摸结束:", touchEndX, touchEndY);
$(this).removeClass("touching");
// 检测手势
detectGesture();
// 恢复位置
$(this).css({
transform: "translate(0, 0)",
transition: "transform 0.3s"
});
// 移除过渡效果
setTimeout(function() {
$("#touch-element").css("transition", "none");
}, 300);
})
// 触摸取消
.on("touchcancel", function(event) {
console.log("触摸取消");
$(this).removeClass("touching");
});
// 手势识别函数
function detectGesture() {
var deltaX = touchEndX - touchStartX;
var deltaY = touchEndY - touchStartY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
var duration = Date.now() - $("#touch-element").data("touchStartTime");
console.log("手势参数:", {
deltaX: deltaX,
deltaY: deltaY,
distance: distance,
duration: duration
});
// 滑动检测
if (Math.abs(deltaX) > 50 && Math.abs(deltaY) < 30) {
if (deltaX > 0) {
console.log("向右滑动");
swipeRight();
} else {
console.log("向左滑动");
swipeLeft();
}
}
// 垂直滑动
if (Math.abs(deltaY) > 50 && Math.abs(deltaX) < 30) {
if (deltaY > 0) {
console.log("向下滑动");
swipeDown();
} else {
console.log("向上滑动");
swipeUp();
}
}
// 轻触检测(快速触摸)
if (distance < 10 && duration < 300) {
console.log("轻触");
handleTap();
}
// 长按检测
if (duration > 1000) {
console.log("长按");
handleLongPress();
}
}
// 多点触控
$("#multi-touch").on("touchstart", function(event) {
var touches = event.originalEvent.touches;
console.log("触摸点数:", touches.length);
if (touches.length === 2) {
// 双指手势
console.log("双指触摸");
var touch1 = touches[0];
var touch2 = touches[1];
// 计算初始距离
var initialDistance = getDistance(
touch1.pageX, touch1.pageY,
touch2.pageX, touch2.pageY
);
$(this).data("initialDistance", initialDistance);
}
});
$("#multi-touch").on("touchmove", function(event) {
var touches = event.originalEvent.touches;
if (touches.length === 2) {
event.preventDefault();
var touch1 = touches[0];
var touch2 = touches[1];
// 计算当前距离
var currentDistance = getDistance(
touch1.pageX, touch1.pageY,
touch2.pageX, touch2.pageY
);
var initialDistance = $(this).data("initialDistance");
var scale = currentDistance / initialDistance;
console.log("缩放比例:", scale);
// 应用缩放
$(this).css("transform", "scale(" + scale + ")");
}
});
function getDistance(x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
}
// 移动端点击延迟处理
// 使用fastclick库或以下方法
$("#mobile-button").on("touchstart click", function(event) {
// 阻止触摸事件后的click事件
if (event.type === "touchstart") {
$(this).data("touchStarted", true);
} else if (event.type === "click" && $(this).data("touchStarted")) {
event.preventDefault();
event.stopPropagation();
$(this).removeData("touchStarted");
return false;
}
// 处理点击逻辑
handleButtonClick();
});
// 检测设备是否支持触摸
function isTouchDevice() {
return 'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
}
if (isTouchDevice()) {
$("body").addClass("touch-device");
console.log("触摸设备");
} else {
$("body").addClass("no-touch");
console.log("非触摸设备");
}
触摸事件演示:
在此区域触摸/点击
(在移动设备或模拟触摸的设备上效果更好)
开始位置: -
结束位置: -
手势: -
触摸点数: 0
设备类型: 检测中...
事件日志:
清空日志
移动端开发建议
同时处理触摸和鼠标事件以确保兼容性
使用event.preventDefault()防止触摸时的默认行为(如滚动)
考虑点击延迟问题,可使用FastClick等库
为触摸事件提供视觉反馈
测试不同尺寸的触摸设备和不同操作系统
事件对象
事件对象包含事件相关的所有信息,在事件处理函数中作为参数传递。
事件对象详解
jQuery事件对象是对原生事件对象的封装,提供跨浏览器兼容的属性。
// 事件处理函数接收事件对象
$(selector).on("click", function(event) {
// 使用事件对象
console.log(event.type);
})
事件对象常用属性:
属性
描述
示例
type
事件类型(如 "click")
event.type
target
触发事件的元素
event.target
currentTarget
当前处理事件的元素
event.currentTarget
relatedTarget
相关元素(如mouseenter的离开元素)
event.relatedTarget
pageX/pageY
鼠标相对于文档的坐标
event.pageX
clientX/clientY
鼠标相对于视口的坐标
event.clientX
screenX/screenY
鼠标相对于屏幕的坐标
event.screenX
which
鼠标按钮或键盘键码
event.which
key/keyCode
键盘按键信息
event.key
shiftKey
是否按下了Shift键
event.shiftKey
ctrlKey
是否按下了Ctrl键
event.ctrlKey
altKey
是否按下了Alt键
event.altKey
metaKey
是否按下了Meta键
event.metaKey
timeStamp
事件发生的时间戳
event.timeStamp
data
通过on()方法传递的数据
event.data
result
上一个事件处理函数的返回值
event.result
originalEvent
原始的事件对象
event.originalEvent
事件对象方法:
方法
描述
示例
preventDefault()
阻止事件的默认行为
event.preventDefault()
stopPropagation()
阻止事件冒泡
event.stopPropagation()
stopImmediatePropagation()
阻止事件冒泡并阻止同类型其他处理函数执行
event.stopImmediatePropagation()
isDefaultPrevented()
检查是否调用了preventDefault()
event.isDefaultPrevented()
isPropagationStopped()
检查是否调用了stopPropagation()
event.isPropagationStopped()
// 事件对象使用示例
$("#element").on("click", function(event) {
// 获取事件信息
console.log("事件类型:", event.type);
console.log("目标元素:", event.target.tagName);
console.log("当前元素:", event.currentTarget.tagName);
console.log("鼠标坐标:", event.pageX, event.pageY);
console.log("时间戳:", event.timeStamp);
// 检查按键状态
if (event.shiftKey) {
console.log("按下了Shift键");
}
if (event.ctrlKey) {
console.log("按下了Ctrl键");
}
// 阻止默认行为
event.preventDefault();
// 阻止事件冒泡
event.stopPropagation();
// 检查方法是否已被调用
console.log("默认行为已阻止?", event.isDefaultPrevented());
console.log("事件冒泡已阻止?", event.isPropagationStopped());
// 访问原始事件对象
console.log("原始事件:", event.originalEvent);
// 返回结果(可供后续事件处理函数访问)
return "处理完成";
});
// 链式事件处理
$("#element")
.on("click", function(event) {
console.log("第一个处理函数");
// 这个返回值可以在event.result中访问
return "第一个返回值";
})
.on("click", function(event) {
console.log("第二个处理函数");
console.log("上一个函数的返回值:", event.result); // "第一个返回值"
return "第二个返回值";
});
// 使用事件数据
$("#button").on("click", {user: "张三", action: "提交"}, function(event) {
console.log("用户:", event.data.user); // "张三"
console.log("操作:", event.data.action); // "提交"
});
// 鼠标事件对象
$("#element").on("mousedown", function(event) {
console.log("鼠标按钮:", event.which); // 1=左键, 2=中键, 3=右键
console.log("按钮:", event.button); // 同上
});
// 键盘事件对象
$("#input").on("keydown", function(event) {
console.log("键码:", event.keyCode);
console.log("键:", event.key);
console.log("代码:", event.code);
console.log("是否重复:", event.repeat);
});
事件对象演示:
点击我查看事件对象信息
事件对象属性:
测试preventDefault()
测试stopPropagation()
事件委托
事件委托是将事件处理程序附加到父元素,利用事件冒泡处理子元素的事件。
事件委托详解
事件委托可以提高性能,特别是对于动态添加的元素。
// 基本语法
$(parentSelector).on(eventType, childSelector, handler)
// 示例
$("#list").on("click", "li", function() {
console.log("列表项被点击");
})
为什么使用事件委托:
// 传统方式 - 直接绑定(不推荐用于动态元素)
$("li").click(function() {
console.log("列表项点击");
});
// 问题:动态添加的li元素不会有点击事件
$("#list").append("新项目"); // 这个新li没有点击事件
// 解决方案1 - 每次添加元素后重新绑定(性能差)
function addListItem(text) {
var $li = $("").text(text);
$("#list").append($li);
$li.click(function() { // 为每个新元素绑定事件
console.log("点击:", text);
});
}
// 解决方案2 - 事件委托(推荐)
$("#list").on("click", "li", function() {
console.log("点击:", $(this).text());
});
// 现在动态添加的li元素也会触发事件
$("#list").append("新项目"); // 这个新li也有点击事件
事件委托的优势:
// 1. 处理动态添加的元素
$("#dynamic-list").on("click", "li.item", function() {
// 即使li.item是动态添加的,也会触发事件
console.log("动态项目被点击");
});
// 动态添加元素
setInterval(function() {
$("#dynamic-list").append('新项目' + Date.now() + '');
}, 1000);
// 2. 减少事件处理程序数量
// 传统方式:为1000个元素绑定1000个事件处理程序
$("td").click(function() { // 性能差
console.log("单元格点击");
});
// 事件委托:只绑定1个事件处理程序
$("#table").on("click", "td", function() { // 性能好
console.log("单元格点击");
});
// 3. 简化事件管理
$("#container").on({
click: function(event) {
var $target = $(event.target);
// 根据目标元素执行不同操作
if ($target.is("button.delete")) {
deleteItem($target.closest(".item"));
} else if ($target.is("button.edit")) {
editItem($target.closest(".item"));
} else if ($target.is(".item")) {
selectItem($target);
}
},
mouseenter: function(event) {
if ($(event.target).is(".item")) {
$(event.target).addClass("hover");
}
},
mouseleave: function(event) {
if ($(event.target).is(".item")) {
$(event.target).removeClass("hover");
}
}
}, ".item, button"); // 只代理.item和button元素的事件
// 4. 内存管理更简单
// 移除元素时不需要特别处理事件
$(".item").remove(); // 事件处理程序仍然在#container上
// 如果要移除所有事件,只需:
$("#container").off();
事件委托示例:
// 表格行操作
$("#data-table").on("click", "td", function(event) {
event.stopPropagation(); // 阻止事件冒泡到tr
var $td = $(this);
var columnIndex = $td.index();
var rowIndex = $td.closest("tr").index();
console.log("点击单元格:", rowIndex, columnIndex);
// 根据不同列执行不同操作
if (columnIndex === 0) {
toggleCheckbox($td);
} else if (columnIndex === 1) {
editCell($td);
} else if (columnIndex === 2) {
deleteRow($td.closest("tr"));
}
});
// 列表操作
$("#todo-list").on({
click: function(event) {
var $target = $(event.target);
if ($target.is(".delete-btn")) {
// 删除项目
$target.closest(".todo-item").remove();
} else if ($target.is(".edit-btn")) {
// 编辑项目
editTodoItem($target.closest(".todo-item"));
} else if ($target.is(".todo-item")) {
// 切换完成状态
$target.toggleClass("completed");
}
},
dblclick: function(event) {
if ($(event.target).is(".todo-text")) {
// 双击文本编辑
editTodoText($(event.target));
}
}
}, ".todo-item, .delete-btn, .edit-btn, .todo-text");
// 表单动态字段
$("#dynamic-form").on({
focus: function() {
$(this).addClass("focused");
},
blur: function() {
$(this).removeClass("focused");
validateField($(this));
},
change: function() {
updateFormState();
}
}, "input, select, textarea"); // 代理所有表单元素
// 动态添加表单字段
$("#add-field").click(function() {
var fieldId = "field-" + Date.now();
$("#dynamic-form").append(
'' +
' ' +
' 删除' +
''
);
});
// 删除动态字段(也需要事件委托)
$("#dynamic-form").on("click", ".remove-field", function() {
$(this).closest(".form-group").remove();
updateFormState();
});
// 性能对比示例
console.time("直接绑定");
for (var i = 0; i < 1000; i++) {
$("项目" + i + "")
.appendTo("#container")
.click(function() { // 为每个元素绑定事件
console.log("点击");
});
}
console.timeEnd("直接绑定"); // 较慢
console.time("事件委托");
$("#container").empty();
for (var i = 0; i < 1000; i++) {
$("项目" + i + "").appendTo("#container");
}
$("#container").on("click", ".item", function() { // 只绑定一次
console.log("点击");
});
console.timeEnd("事件委托"); // 较快
事件委托演示:
添加列表项
删除第一项
添加100项
项目1 删除
项目2 删除
项目3 删除
列表项数量: 3
点击计数: 0
事件日志:
事件委托的注意事项
事件委托利用事件冒泡,如果事件不冒泡(如focus、blur),需要使用focusin/focusout
过度使用事件委托可能导致事件处理逻辑复杂
对于简单、静态的元素,直接绑定可能更清晰
事件委托的目标选择器应尽可能具体,避免性能问题
使用event.stopPropagation()要谨慎,可能影响其他事件处理
事件流
事件流描述事件在DOM树中传播的顺序。
事件冒泡与捕获
DOM事件流包括三个阶段:捕获阶段、目标阶段、冒泡阶段。
捕获阶段
文档 → body → div
↓
目标阶段
button
↑
冒泡阶段
div → body → 文档
// 事件流演示
// 默认情况下,事件处理在冒泡阶段执行
$("#child").click(function(event) {
console.log("子元素 - 目标阶段");
});
$("#parent").click(function(event) {
console.log("父元素 - 冒泡阶段");
});
$("#grandparent").click(function(event) {
console.log("祖父元素 - 冒泡阶段");
});
$(document).click(function(event) {
console.log("文档 - 冒泡阶段");
});
// 点击按钮时输出顺序:
// 1. 子元素 - 目标阶段
// 2. 父元素 - 冒泡阶段
// 3. 祖父元素 - 冒泡阶段
// 4. 文档 - 冒泡阶段
// 使用捕获阶段
// jQuery默认不支持捕获阶段,需要使用原生addEventListener
document.getElementById("grandparent").addEventListener("click", function(event) {
console.log("祖父元素 - 捕获阶段");
}, true); // true表示在捕获阶段处理
document.getElementById("parent").addEventListener("click", function(event) {
console.log("父元素 - 捕获阶段");
}, true);
// 现在点击按钮时输出顺序:
// 1. 祖父元素 - 捕获阶段
// 2. 父元素 - 捕获阶段
// 3. 子元素 - 目标阶段(jQuery)
// 4. 父元素 - 冒泡阶段(jQuery)
// 5. 祖父元素 - 冒泡阶段(jQuery)
// 6. 文档 - 冒泡阶段(jQuery)
// 阻止事件冒泡
$("#child").click(function(event) {
console.log("子元素点击");
event.stopPropagation(); // 阻止事件冒泡
// 现在只有这个处理函数会执行
});
$("#parent").click(function(event) {
console.log("这个不会执行,因为事件冒泡被阻止了");
});
// 阻止默认行为
$("a").click(function(event) {
event.preventDefault(); // 阻止链接跳转
console.log("链接被点击,但不会跳转");
});
// 同时阻止默认行为和冒泡
$("a").click(function(event) {
event.preventDefault();
event.stopPropagation();
// 或者使用 return false
// return false; // 等同于上面两行
});
// 事件委托中的事件流
$("#grandparent").on("click", "#child", function(event) {
console.log("事件委托处理");
// 事件仍然会冒泡到#grandparent
});
// 检查事件阶段
document.getElementById("child").addEventListener("click", function(event) {
console.log("事件阶段:", event.eventPhase);
// 1: 捕获阶段
// 2: 目标阶段
// 3: 冒泡阶段
}, true);
// 事件委托和事件流的结合
$("#container").on("click", function(event) {
console.log("容器点击,目标:", event.target.tagName);
// 根据event.target决定如何处理
if ($(event.target).is("button")) {
console.log("点击了按钮");
} else if ($(event.target).is("input")) {
console.log("点击了输入框");
}
});
// 事件流可视化
function logEventFlow(event) {
var phase;
switch(event.eventPhase) {
case Event.CAPTURING_PHASE: phase = "捕获"; break;
case Event.AT_TARGET: phase = "目标"; break;
case Event.BUBBLING_PHASE: phase = "冒泡"; break;
}
console.log(event.currentTarget.id + " - " + phase + "阶段");
}
// 为所有元素添加事件监听
$("#grandparent, #parent, #child").each(function() {
this.addEventListener("click", logEventFlow, true); // 捕获阶段
this.addEventListener("click", logEventFlow, false); // 冒泡阶段
});
事件流演示:
文档
body
div#grandparent
div#parent
button#child
点击我
阻止事件冒泡
阻止立即传播
事件流日志:
清空日志
自定义事件
自定义事件允许创建和触发自己的事件类型。
创建和触发自定义事件
自定义事件可以实现组件间的松耦合通信。
// 绑定自定义事件
$(selector).on("customEvent", handler)
// 触发自定义事件
$(selector).trigger("customEvent"[, extraParameters])
// 使用命名空间
$(selector).on("customEvent.namespace", handler)
// 创建带数据的事件
var event = $.Event("customEvent", {data: value})
基本自定义事件:
// 创建自定义事件
$("#element").on("myCustomEvent", function(event, param1, param2) {
console.log("自定义事件触发", param1, param2);
});
// 触发自定义事件
$("#element").trigger("myCustomEvent", ["参数1", "参数2"]);
// 带命名空间的自定义事件
$("#element").on("myEvent.namespace1", function() {
console.log("命名空间1的事件");
});
$("#element").on("myEvent.namespace2", function() {
console.log("命名空间2的事件");
});
// 触发特定命名空间的事件
$("#element").trigger("myEvent.namespace1");
// 移除特定命名空间的事件
$("#element").off(".namespace1");
// 创建自定义事件对象
var customEvent = $.Event("customEvent", {
pageX: 100,
pageY: 200,
data: {key: "value"}
});
// 触发带自定义数据的事件
$("#element").trigger(customEvent);
// 在事件处理函数中访问自定义数据
$("#element").on("customEvent", function(event) {
console.log("自定义数据:", event.data); // {key: "value"}
console.log("页面坐标:", event.pageX, event.pageY); // 100, 200
});
自定义事件应用场景:
// 1. 组件间通信
// 购物车组件
var ShoppingCart = {
init: function() {
this.bindEvents();
},
bindEvents: function() {
// 监听商品添加事件
$(document).on("productAdded", function(event, product) {
ShoppingCart.addProduct(product);
});
// 监听商品移除事件
$(document).on("productRemoved", function(event, productId) {
ShoppingCart.removeProduct(productId);
});
},
addProduct: function(product) {
console.log("添加商品:", product);
// 更新购物车UI
this.updateUI();
// 触发购物车更新事件
$(document).trigger("cartUpdated", [this.getCartItems()]);
},
removeProduct: function(productId) {
console.log("移除商品:", productId);
// 更新购物车UI
this.updateUI();
// 触发购物车更新事件
$(document).trigger("cartUpdated", [this.getCartItems()]);
},
updateUI: function() {
// 更新购物车显示
},
getCartItems: function() {
// 返回购物车商品列表
return [];
}
};
// 商品列表组件
$(".add-to-cart").click(function() {
var product = {
id: $(this).data("id"),
name: $(this).data("name"),
price: $(this).data("price")
};
// 触发商品添加事件
$(document).trigger("productAdded", [product]);
});
// 2. 状态变化通知
var ApplicationState = {
currentUser: null,
login: function(user) {
this.currentUser = user;
// 触发登录事件
$(document).trigger("userLoggedIn", [user]);
},
logout: function() {
var oldUser = this.currentUser;
this.currentUser = null;
// 触发登出事件
$(document).trigger("userLoggedOut", [oldUser]);
}
};
// 监听登录状态变化
$(document).on("userLoggedIn", function(event, user) {
console.log("用户登录:", user.name);
updateUIForLoggedInUser(user);
});
$(document).on("userLoggedOut", function(event, user) {
console.log("用户登出:", user.name);
updateUIForLoggedOutUser();
});
// 3. 异步操作完成通知
function loadUserData(userId) {
// 模拟异步加载
setTimeout(function() {
var userData = {
id: userId,
name: "张三",
email: "zhangsan@example.com"
};
// 数据加载完成,触发事件
$(document).trigger("userDataLoaded", [userData]);
}, 1000);
}
// 监听数据加载完成
$(document).on("userDataLoaded", function(event, userData) {
console.log("用户数据加载完成:", userData);
renderUserProfile(userData);
});
// 开始加载数据
loadUserData(123);
// 4. 表单验证事件
$("#registration-form").on("submit", function(event) {
event.preventDefault();
var formData = $(this).serializeArray();
var errors = validateFormData(formData);
if (errors.length === 0) {
// 表单验证通过,触发事件
$(this).trigger("formValid", [formData]);
} else {
// 表单验证失败,触发事件
$(this).trigger("formInvalid", [errors]);
}
});
// 监听表单验证结果
$("#registration-form")
.on("formValid", function(event, formData) {
console.log("表单验证通过,提交数据");
submitForm(formData);
})
.on("formInvalid", function(event, errors) {
console.log("表单验证失败:", errors);
displayErrors(errors);
});
// 5. 自定义事件命名空间管理
// 为不同模块使用不同命名空间
var UserModule = {
namespace: "userModule",
init: function() {
this.bindEvents();
},
bindEvents: function() {
$(document).on("login." + this.namespace, this.onLogin.bind(this));
$(document).on("logout." + this.namespace, this.onLogout.bind(this));
},
onLogin: function(event, user) {
console.log("用户模块: 用户登录", user);
},
onLogout: function(event, user) {
console.log("用户模块: 用户登出", user);
},
destroy: function() {
// 清理事件
$(document).off("." + this.namespace);
}
};
var CartModule = {
namespace: "cartModule",
init: function() {
this.bindEvents();
},
bindEvents: function() {
$(document).on("productAdded." + this.namespace, this.onProductAdded.bind(this));
},
onProductAdded: function(event, product) {
console.log("购物车模块: 商品添加", product);
},
destroy: function() {
// 清理事件
$(document).off("." + this.namespace);
}
};
// 初始化模块
UserModule.init();
CartModule.init();
// 触发事件(所有模块都会收到)
$(document).trigger("login", [{id: 1, name: "张三"}]);
$(document).trigger("productAdded", [{id: 101, name: "商品A"}]);
// 销毁特定模块
UserModule.destroy(); // 只移除用户模块的事件
自定义事件演示:
触发自定义事件
触发命名空间事件
触发带数据的事件
添加事件处理程序
移除事件处理程序
移除所有自定义事件
自定义事件日志:
清空日志
综合演示
综合演示各种事件处理技术。
事件处理综合演示
交互式演示面板
鼠标事件区域
在此区域交互
等待交互...
重置区域
交互次数: 0
事件计数器
点击
0
鼠标移动
0
按键
0
输入
0
重置计数器
动态列表(事件委托)
添加
示例项目1 ×
示例项目2 ×
项目数量: 2
事件控制面板
启用鼠标事件
启用键盘事件
启用事件日志
触发所有事件
移除所有事件
综合事件日志
导出日志
清空日志
日志条目: 0
事件处理总结
事件绑定: 使用 on() 方法绑定事件,off() 方法移除事件
事件类型: 鼠标事件、键盘事件、表单事件、窗口事件、触摸事件
事件对象: 包含事件所有信息,可使用 preventDefault() 和 stopPropagation()
事件委托: 使用 $(parent).on(event, childSelector, handler) 处理动态元素
事件流: 事件传播的三个阶段:捕获、目标、冒泡
自定义事件: 使用 trigger() 触发自定义事件,实现组件间通信
最佳实践: 使用事件委托提高性能,合理使用事件命名空间,注意内存管理
上一章: DOM 操作与修改
下一章: jQuery 动画与效果