JS21 - JQuery - 详细入门+实例:原生tab功能 手风琴效果 购物车页面

1,660 阅读19分钟

jQuery 是什么

  • jQuery 本质:是一个快速,轻量级且功能丰富的 JavaScript 方法库,不是常说的前端框架,前端框架一般来说主要还是 VUE、React、Angular。
  • jQuery 优点:选择器、隐式迭代、链式编程、解决兼容(jquery1功能,jquery3已经废弃),从而简化了HTML文档的 遍历、事件、动画和Ajax交互,从而实现了快速的Web开发
  • jQuery 特色:一行代码,少写多做
  • jQuery 官网jquery.com/

jQuery

基本语法

$(选择器).执行方法();

<head>
  <script src="./jquery-3.7.1.min.js"></script>
  <script>
    document.addEventListener("DOMContentLoaded", () => {
      // 原生 js 写法
      const btn = document.getElementById("btn");
      btn.onclick = () => {
        document.createElement("div");
        div.style.width = "200px";
        div.style.height = "200px";
        div.style.backgroundColor = "red";
        document.body.appendChild(div);
      }
    })

    $().ready(() => {
      // jquery 写法
      $("#btn").click(() => {
        $("<div></div>").css({
          width: "200px",
          height: "200px",
          backgroundColor: "red"
        }).appendTo($("body"))
      })
    })
  </script>
</head>

<body>
  <button id="btn">click</button>
</body>

解决 $ 冲突

  • noConflict() 方法会释放对 $ 标识符的控制,使得 $ 变成 undefined
var jq = jQuery.noConflict(); 
jq(document).ready(function(){ 
    jq("button").click(function(){ 
        jq("p").text("jQuery 仍然在工作!"); 
    }); 
});

//如果你的 jQuery 代码块使用 $ 简写,并且您不愿意改变这个快捷方式,那么您可以把 $ 符号作为变量传递给 ready 方法。这样就可以在函数内使用 $ 符号了 - 而在函数外,依旧不得不使用 "jQuery"
$.noConflict(); 
jQuery(document).ready(function($){ 
        $("button").click(function(){ 
        $("p").text("jQuery 仍然在工作!"); 
    }); 
});

入口函数

  • ready:在 html 所有标签(DOM)都加载之后,就会去执行。
  • JavaScript 入口函数window.onload 事件 是等到所有内容,包括外部图片之类的文件加载完后,才会执行,因此如果遇到有图片加载的问题,就会存在页面阻塞。
//jQuery 入口函数
$(document).ready(function(){ /*...*/ })
$(function(){ /*...*/ })
// 这个入口函数是对 js 事件 DOMContentLoaded 的封装,但执行要比 ready 入口函数更快
document.addEventListener("DOMContentLoaded", function (event) {/*...*/})

//JavaScript 入口函数
window.onload = function(){ /*...*/ }

链式调用

jquery 在执行内置方法后,会返回当前的 jQuery 对象本身

// 这里的addClass()、css()和slideDown()方法在执行后都返回$('#myElement'),因此可以被连续调用
$('#myElement').addClass('active').css('color', 'red').slideDown();

选择器

jQuery 选择器返回一个伪数组,包含 length 属性

共同生效
//同 CSS - 常用
$("*");                 //-> 所有
$("#idName");           //-> id="idName"
$("div");               //-> 所有 div 标签
$(".intro,.demo");      //-> class 为 "intro" 或 "demo" 的所有元素
$("div:not(p)");        //-> div中所有非p的元素,但如果p没有设置样式也会受到影响
$(":root");             //-> 文档的根元素 html
$(":header");           //-> 所有 h1 - h6,但 h7-h9 也会匹配上

$("div > p");                   // div 直接子元素的所有 p 
$("div p");                     // div 的所有后代 p 
$("div + p");                   // div 相邻的下一个 p,同jQuery的 next("p") 方法
$("div ~ p");                   // div 同级的所有 p,同jQuery的 siblings("p") 方法

$("p:first-child");             //(所有元素中计数)-> 作为某个父元素的 第一个子元素(匹配满足这个条件的 第一个 p)
$("p:first-of-type");           //(所有元素中计数)-> 作为某个父元素的 第一个子元素(匹配满足这个条件的 所有 p)
$("p:last-child");              //(所有元素中计数)-> 作为某个父元素的 最后一个子元素(匹配满足这个条件的 第一个 p)
$("p:last-of-type");            //(所有元素中计数)-> 作为某个父元素的 最后一个子元素(匹配满足这个条件的 所有 p)
$("p:nth-child(2)");            //(所有元素中计数)-> 作为某个父元素的 第二个子元素(匹配满足这个条件的 所有 p)
$("p:nth-last-child(2)");       //(所有元素中计数)-> 作为某个父元素的 倒第二个子元素(匹配所有满足这个条件的 所有 p)
$("p:only-child");              //(所有元素中计数)-> 作为某个父元素的 p 元素(p是唯一的子元素,且只有一个)

$("p:nth-of-type(2)");          //(只在p元素中计数)-> 作为某个父元素的 所有第二个 p 元素
$("p:nth-last-of-type(2)");     //(只在p元素中计数)-> 作为某个父元素的 所有倒第二个 p 元素
$("p:only-of-type");            //(只在p元素中计数)-> 作为某个父元素的 那个唯一的 p 元素

$("p:lang(de)");                // 所有 lang 属性值为 "de" 的 p 元素
$("[href]");                    // 所有带有 href 属性的元素
$("[href='default.htm']");      // 所有带有 href 属性且值等于 "default.htm" 的元素
$("[href!='default.htm']");     // 所有带有 href 属性且值不等于 "default.htm" 的元素
$("[href$='.jpg']");            // 所有带有 href 属性且值以 ".jpg" 结尾的元素
$("[title^='Tom']");            // 所有带有 title 属性且值以 "Tom" 开头的元素
$("[title|='Tomorrow']");       // 所有带有 title 属性且值等于 'Tomorrow' 或者以 'Tomorrow' 后跟连接符作为开头的字符串
$("[title~='hello']");          // 所有带有 title 属性且值包含单词 "hello" 的元素
$("[title*='hello']");          // 所有带有 title 属性且值包含字符串 "hello" 的元素
$("input[id][name$='man']");    // 带有 id 属性,并且 name 属性以 man 结尾的 input

$("a:link");            //-> 所有未访问链接
$("#news:target");         //-> 当前活动的#news元素(包含该锚名称的点击的URL)
$(":empty");            //-> 所有空元素(不含子元素或文本)

//同 CSS - 表单常用
$(":input");            //-> 所有 input 元素
$(":text");             //-> 所有带有 type="text" 的 input 元素
$(":password");         //-> 所有带有 type="password" 的 input 元素
$(":radio");            //-> 所有带有 type="radio" 的 input 元素
$(":checkbox");         //-> 所有带有 type="checkbox" 的 input 元素
$(":submit");           //-> 所有带有 type="submit" 的 input 元素
$(":reset");            //-> 所有带有 type="reset" 的 input 元素
$(":button");           //-> 所有带有 type="button" 的 input 元素
$(":image");            //-> 所有带有 type="image" 的 input 元素
$(":file");             //-> 所有带有 type="file" 的 input 元素
$("input:not(:empty)"); //-> 所有不为空的input
$(":focus");            //-> 当前 焦点的元素
$(":enabled");          //-> 所有启用的元素
$(":disabled");         //-> 所有禁用的元素
$(":selected");         //-> 所有选定的(option 具有 selected 属性)下拉列表元素
$(":checked");          //-> 所有选中的复选框选项
$("input:in-range")     //标签的值在指定区间之外时显示的样式,例如 type="number" value="11" min="10" max="12
$("input:out-of-range");//标签的值在指定区间之外时显示的样式,例如 type="number" value="15" min="10" max="12
$("input:valid")	//用于匹配输入值为合法的元素	
$("input:invalid")	//用于匹配输入值为非法的元素
$("input:optional")	//用于匹配可选的输入元素	
$("input:required")	//匹配设置了 "required" 属性的元素
仅 CSS
//CSS 有效,jQuery无效
$(":visited");          //无效:选择所有访问过的链接
$(":active");           //无效:选择活动链接
$(":hover");            //无效:选择鼠标在链接上面时
$("p:first-letter");    //无效:所有 p 的第一个字母
$("p:first-line");      //无效:所有 p 的第一行	1
$("p:before");          //无效:在每个<p>元素之前插入内容	2
$("p:after");           //无效:在每个<p>元素之后插入内容
$("p::selection");      //无效 匹配元素中被用户选中或处于高亮状态的部分的属性 color, background, cursor,和outline
$("input:ready-write"); //无效 标签的值在指定区间之外时显示的样式,例如 type="number" value="15" min="10" max="12
$("input:ready-only");  //无效 标签的值在指定区间之外时显示的样式,例如 type="number" value="15" min="10" max="12
仅 JQuery
//jQuery 特有
$("p:first");           //-> 第一个 <p> 元素
$("p:last");            //-> 最后一个 <p> 元素

$("tr:even");           //-> 从 0 开始,所有偶数 tr 元素
$("tr:odd");            //-> 从 1 开始,所有奇数 tr 元素

$("p:eq(3)");           //-> 第4个p(从0开始计数)
$("p:gt(3)");           //-> 第3个之后的所有 p
$("p:lt(3)");           //-> 第3个之前的所有 p

$(":animated");         //-> 所有使用了animate()方法的动画元素,css定义的动画不匹配
$(":contains('ok')");   //-> 所有包含文本 "ok" 的元素
$("div:has(p)");        //-> 后代元素中包含 p 的 div
$(":parent");           //-> 所有非空元素(父元素,含有子元素或者文本,区别表单的空)
$("p:hidden");          //-> 所有隐藏的 p -> (隐藏指:display:none 或 type="hidden"表单,该选择器对 visibility:hidden 和 opacity: 0 的元素不起作用)
$("p:visible");         //-> 所有可见的 p -> (可见是非以下情况: (1)display:none (2)type="hidden" 表单 (3)width 和 height 设置为 0)

元素节点

获取

父级

  • $(selector).parent(filter) 返回直接父级
  • $(selector).parents(filter) 返回所有祖先元素,一路向上直到文档的根元素 html
  • $(selector).parentsUntil(filter) 返回介于两个给定元素之间的所有祖先元素

子级

  • $(selector).children(filter) 返回被选元素的所有直接子元素
  • $(selector).find(filter) 返回与指定参数匹配的所有后代元素

同级

  • $(*selector*).index(element) 返回指定元素相对于其他指定元素的 index 位置,如果未找到元素,index() 将返回 -1
  • $(*selector*).slice(*start,stop*) slice() 方法选取基于索引的元素的子集,参数 start(必需),规定开始选取元素的位置,索引号从 0 开始,如果是负数,从末端开始选,stop(可选),结束位置,如果省略,则末端结束,索引号从 0 开始,如果是负数,从末端开始
  • $(selector).siblings(filter) 返回所有同级元素(selector 如果是同一级,则不包含自身;selector 如果是先指向父类,然后指向后代,则会返回所有)
  • $(selector).next(filter) $(selector).prev(filter) next() 返回同级的下一个元素;prev()方向相反
  • $(selector).nextAll(filter) $(selector).prevAll(filter)nextAll() 返回后面同级的所有元素;prevAll()方向相反
  • $(selector).nextUntil(filter) $(selector).prevUntil(filter) nextUntil() 返回介于两个给定参数之间的后面所有的同胞元素,prevUntil() 方向相反

过滤

  • $(selector).first(filter) $(selector).last(filter) 返回选择器指定的首个/最后一个元素(不包括 selector 自身)
  • $(selector).eq(filter) 返回选择器指定的第 n 个元素,索引号从 0 开始
  • $(selector).filter(filter) $(selector).not(filter) filter 返回匹配的;not 返回不匹配的;具体规则可以自由指定;not() 方法与 filter() 相反。
$("p").parent();                //返回 p 的直接父级
$("p").parent(".selected");     //返回带有 selected 类的 p 的直接父元素

$("span").parents();            //返回所有 <span> 元素的所有祖先
$("span").parents("ul");        //返回所有 <span> 元素的所有祖先,并且它是 <ul> 元素

$("span").parentsUntil("div");  //返回介于 <span> 与 <div> 元素之间的所有祖先元素
$("div").children();       //返回DIV的直接子元素
$("div").children("p");    //返回DIV的直接子代的所有<p>元素

$("p").find("span");       //返回属于 p 后代的所有 span 元素
$("div").find("*");        //返回 div 的所有后代
$("h2").siblings();//返回 <h2> 的所有同胞元素
$("h2").siblings("p");  //返回属于 <h2> 的同胞元素的所有 <p> 元素

$("h2").next();  //返回 <h2> 的下一个同胞元素
$("h2").nextAll();  //返回 <h2> 的所有跟随的同胞元素
$("h2").nextUntil("h6"); //返回介于 <h2> 与 <h6> 元素之间(不包括 h2 和 h6)的所有同胞元素

<li>111</li> <li>222</li> <li>333</li> <li>444</li>

Array.from($("li").nextAll("li:nth-of-type(3)")).map(_=>_.innerText);  // [333]
Array.from($("li").nextUntil()).map(_=>_.innerText); // [222,333,444] -> 从选择器的第二个开始到末尾
Array.from($("li:nth-of-type(1)").nextUntil("li:nth-of-type(3)")).map(_=>_.innerText); // [222]

Array.from($("li").prevAll()).map(_=>_.innerText); // [333,222,111] -> 从选择器的倒数第二个往上/往前,直到最上面/前面那个
Array.from($("li").prevUntil()).map(_=>_.innerText); // [333,222,111] -> 从选择器的倒数第二个往上/往前,直到给定选择器条件的下一个(如果不指定,则会到底)
Array.from($("li").prevUntil("li:nth-of-type(2)")).map(_=>_.innerText);  // [333] -> 从选择器倒数第二个开始,往上/往前,直到给定选择器条件的最前面一个

$("li").click(function(){ console.log($(this).index()) });  //获取当前点击的下标
console.log($(".hot").index($("#favorite")));   //获取class为hot的元素中id为favorite的下标

$("p").slice().css("background-color","yellow");  //标黄所有 <p> 元素
$("p").slice(2).css("background-color","yellow");  //标黄索引 2 以及后面的 <p> 元素
$("p").slice(2,4).css("background-color","yellow");  //标黄索引 2 到索引 4 的 <p> 元素
$("li").first()[0].innerText;       //111
$("li").last()[0].innerText;        //444
$("li").eq(2)[0].innerText;         //333

$("p").filter(".url");   //返回带有类名 "url" 的所有 <p> 元素
$("p").not(".url");      //返回不带有类名 "url" 的所有 <p> 元素

//not 和 eq 可以实现反选的效果,选取索引值不为 1 的 p 元素,并把背景颜色设置为黄色
$("p").not(":eq(1)").css("background-color","yellow");
内容
  • html() - innerHTML 设置或返回所选元素的内容(包括 HTML 标签),获取的时候只获取第一个,设置的时候会隐式迭代
  • text() - innerText 设置或返回所选元素的文本内容,获取和设置的时候都会隐式迭代
  • val() - value 设置或返回表单字段的值,返回第一个输入字段的值, 获取的时候只获取第一个,设置的时候会隐式迭代
  • 回调函数的参数说明:以上三者都拥有回调函数,且有 两个参数
    • 参数 1:被选元素列表中 当前元素的下标
    • 参数 2初始值
    • 回调函数返回:一个字符串,相当于放在方法中设置的值
//获取
$("#test").text()); //等于 innerText
$("#test").html()); //等于 innerHTML
$("#test").val());  //获取表单 value

$("li").html();  //list
$("li").text();  //listlistlistlistlist
$("input").val();//content

//设置
$("#test1").text("Hello world!");
$("#test2").html("<b>Hello world!</b>");
$("#test3").val("Hello world!");

//携带回调函数
<ul>
    <li>111 <button>btn</button></li>
    <li>222 <button>btn</button></li>
</ul>
$("li").text(function (index, originalText) {
    return `第${index}个元素的初始值为${originalText},回调函数return的结果作为新值放在方法中`;
});
//页面更新为:
// 第0个元素的初始值为111 btn,回调函数return的结果作为新值放在方法中
// 第1个元素的初始值为222 btn,回调函数return的结果作为新值放在方法中
$("li").html(function (index, originalText) {
    return `第${index}个元素的初始值为${originalText},回调函数return的结果作为新值放在方法中`;
});
//页面更新为:
// 第0个元素的初始值为111 btn(这个btn被解析成了元素),回调函数return的结果作为新值放在方法中
// 第1个元素的初始值为222 btn(这个btn被解析成了元素),回调函数return的结果作为新值放在方法中
<input type="text" value="10" />
<input type="text" value="11" />
$("input").val(function(index, originalText){
    return(`第${index}个元素的初始值为${originalText},回调函数return的结果作为新值放在方法中`);
});
//页面input输入框的内容更新为回调函数的返回值
属性
  • attr() - 一般操作 自定义属性,但也能操作原生属性,只是不推荐
  • prop() - 一般操作 原生属性,如果非要操作自定义属性,会把自定义属性添加到原生属性上,后面操作获取就是通过对象获取的
  • removeAttr() - 移除属性,可使用字符串,如果移除多个属性,在一个字符串中用空格隔开,不能使用回调函数
  • 回调函数的参数说明:attr() prop() 都拥有回调函数,且有 两个参数
    • 参数 1:被选元素列表中 当前元素的下标
    • 参数 2初始值
    • 回调函数返回:一个字符串,相当于放在方法中设置的值,这个回调函数是逐个地对集合元素采取操作,如果是要获取整个集合,把每个元素组合起来当成整理看待,则需要跳出这个函数外,再从第三方进行操作(见示例-多行关键词菜单)
//设置和获取自定义属性
$("input").attr("data-index","0");   
$("input").attr("data-index");       // 0 当该方法用于**返回**属性值,只返回第一个匹配元素的值
$("input").attr("data-empty");       // undefined 不存在的属性返回 undefined
$("a").attr({
    "data-href" : "http://jquery.com", 
    "data-index" : "0" }
);
$("a").attr("data-href", function(index,origValue){
    return origValue + "/jquery"; 
});

//设置和获取原生属性
//说明:与DOM中setAttribute()不同的是,prop("disabled",true/false) 有效 
t($("input").prop("disabled",true));    //禁止有效
t($("input").prop("disabled"));         //true   返回第一个匹配元素的值
t($("input").prop("disabled",false));   //解禁有效
t($("input").prop("disabled"));         //false
$("input").prop("disabled",function(index, originalValue){
    console.log(`第${index}个元素的初始值为${originalValue},回调函数return的结果作为新值放在方法中`);
    return true;
});
//页面结果:input被禁用了

//移除属性
$("input").removeAttr("min max");  //解除input的输入限制
插入
  • append() - 在被选元素的结尾插入内容(元素内部
  • appendTo() - 将被选元素的插入到另外一个元素内(元素内部
  • prepend() - 在被选元素的开头插入内容(元素内部
  • after() - 在被选元素之后插入内容(元素外面
  • before() - 在被选元素之前插入内容(元素外面
  • remove() - 删除被选元素(及其子元素),扔掉桶的做法,可传参实现过滤删除
  • empty() - 从被选元素中删除子元素,倒掉水的做法
  • clone() - 方法生成被选元素的副本,包含子节点、文本和属性
//append() 和 prepend() 方法能够通过参数接收无限数量的新元素
function appendText(){ 
    var txt1="<p>文本-1</p>";              // 使用 HTML 标签创建文本 
    var txt2=$("<p></p>").text("文本-2");  // 使用 jQuery 创建文本 
    $("body").append(txt1,txt2,"<p>文本-3</p>");      // 追加新元素 
}

//after() 和 before() 方法能够通过参数接收无限数量的新元素
function afterText() { 
    var txt1="<b>I </b>"; // 使用 HTML 创建元素 
    var txt2=$("<i></i>").text("love "); // 使用 jQuery 创建元素 
    $("img").after(txt1,txt2,"<b>you</b>"); // 在图片后添加文本 
}
//删除
$("#div1").remove();      //删除被选元素及其子元素
$("p").remove(".italic"); //过滤:删除除了类名为italic的p元素,remove是删除自身,因此过滤器中条件只能作用于同级,不能作用于子元素
$("#div1").empty();       //删除被选元素的子元素

//复制
$("body").append($("p:first").clone(true));  //true 复制事件处理程序,alse(默认)不复制事件处理程序
样式
  • css() - 设置或返回样式属性
  • addClass() - 向被选元素添加一个或多个类
  • removeClass() - 从被选元素删除一个或多个类
  • toggleClass() - 对被选元素进行添加/删除类的切换操作
  • hasClass() - 判断是否包含某个类(如果是多个元素,只要有一个满足就返回 true)
  • 说明
    1. css() 方法:样式都添加在行内属性;具有隐式迭代功能。例如,选中多个元素,会自动给每个设置好样式
    2. css() 方法:不能直接设置部分伪类伪元素样式,可通过 $(选择器).append("<style>选择器::after{display:none}</style>"); 方式间接实现
    3. 操作 class:能够隐式迭代;操作 class 的方法,添加到 class 中定义的属性,因此相比于 css 将属性添加到行内,权重更低
//获取和设置 css 样式
$("p").css("background-color");
$("p").css("background-color","yellow");
$("p").css({
    "background-color":"yellow",
    "font-size":"200%"
});
$("div").css({
    width:"-=30px",
    height:"+=10px"     //可以取相对值,在原来的基础上增加或减少
});

//添加多个类
$("body div:first").addClass("important blue");
//第一个参数表示要添加或删除的类,既可以用类列表,也可以用函数返回值指定(i 是选择器选中的所有元素中当前对象的索引值,c 是当前对象的类名)
// switch: 布尔值,true 表示只添加,false 表示只删除
$("body div:first").addClass('c1 c2 ...' | function(i, c));     // 添加一个或多个类
$("body div:first").removerClass('c1 c2 ...' | function(i, c)); // 删除一个或多个类。
$("body div:first").toggleClass('c1 c2 ...' | function(i, c), switch);  // 切换一个或多个类

$("li").eq(1).addClass("before").siblings().removeClass("after");    //指定修改 li 元素中第二个的class样式
$("li").removeClass("before").eq(2).addClass("after");               //指定修改 li 元素中第三个的class样式
尺寸
  • width() height() - content:设置或返回元素的宽/高(不包括内边距、边框或外边距),类似 DOM 中的 offsetWidth offsetHeight,但在 display: none; 时,依然能获取到
  • innerWidth() innerHeight() - content+padding:返回元素的宽高(包括内边距),类似 DOM 中的 clientWidth clientHeight
  • outerWidth() outerHeight() - content+padding+border:返回元素的宽高(包括内边距和边框)
  • outerWidth(true) outerHeight(true) - content+padding+border+margin:返回元素的宽高(包括内边距、边框和外边距)
  • 注意
    • 如果设置了 box-sizing 后,width() 获取的是 css 设置的 width 减去 padding 和 border 的值
    • 即使设置了 display: none;,依然能获取到

布局.png转存失败,建议直接上传图片文件

$("li").width(); 
$("li").eq(1).height();

//对于设置了 box-sizing 
//.test{width:100px;height:100px;padding:10px;border:10px;box-sizing:border-box;}
$(".test").width();      // 60
$(".test").innerWidth(); // 80
$(".test").outWidth();   // 100
布局
  • offset() - 获取距离文档流左上角的 left、top,如果传参数就是设置,相当于是在行内添加了一个 relative 的相对定位样式
  • position() - 有定位的元素的 left、top(相对于它的父元素),只能获取不能设置,该方法返回一个带有两个属性(以像素为单位的 top 和 left 位置)的对象
  • 说明:如果有多个元素,只获取第一个
//返回偏移坐标:$(*selector*).offset()
$("p").offset();
$("li").offset();            //{top: 8, left: 48}
$("li").eq(1).offset();      //{top: 29, left: 48}

//设置偏移坐标:$(*selector*).offset({top:*value*,left:*value*})
$(".relative .absolute").offset();   //{top: 329, left: 208}
$(".relative .absolute").offset({left:50,top:50});
$(".relative .absolute").offset();   //{top: 50, left: 50}
newPos=new Object();
newPos.left="0";
newPos.top="100";
$("p").offset(newPos);
//使用函数设置偏移坐标:$(*selector*).offset(function *(index,currentoffset)* )
$("p").offset(function(n,c){
    newPos=new Object();
    newPos.left=c.left+100;
    newPos.top=c.top+100;
    return newPos;
});

//获取 <p> 元素在 <div> 元素内部的位置
<div style="border:1px solid black;padding:100px;margin:50px;">
    <p>这一段的位置(相对于它的父元素)是<span id="span1">x</span> top 和 <span id="span2">x</span> left.</p>
</div>
x=$("p").position();
$("#span1").text(x.top);
$("#span2").text(x.left);

动画效果

显隐动画
  • hide() - 隐藏;
  • show() - 显示;
  • toggle() - 切换;
  • 底层以 display:none/block; 实现
  • 语法: $(*selector*).hide(speed,timing-function,callback);
  • 可选参数 speed:隐藏/显示的速度,可以三类值:slow、fast、毫秒
  • 可选参数 timing-function:字符串,效果过渡类型,只能取值 linear ,其它报错
  • 可选参数 callback:隐藏或显示完成后所执行的函数,如果调用 callback 时,在函数名后直接加括号,会立刻执行函数体,而不是等到显示/隐藏完成后才执行,而且只会 执行一次,只有当作回调参数的时候才会有多个元素而 执行多次
$("div").hide(1000,"linear",function(){ 
    alert("Hide() 方法已完成!"); 
});
渐变动画
  • fadeOut() - 淡出;
  • fadeIn() - 淡入;
  • fadeToggle() - 切换;
  • fadeTo() - 指定渐变;
  • 底层以 display:none/block;opacity:[number]; 实现
  • 语法$(*selector*).fadeToggle(*speed,timing-function,callback*); $(*selector*).fadeTo(speed,opacity,timing-function,callback);
  • 参数:fadeTo() 没有默认参数,必须加上 slow/fast/Time
  • 可选参数 speed:隐藏/显示的速度,可以三类值:slow、fast、毫秒
  • 可选参数 opacity:fadeTo() 方法,表示给定的不透明度(值介于 0 与 1 之间)
  • 可选参数 timing-function:运动曲线
  • 可选参数 callback:隐藏或显示完成后所执行的函数,如果调用 callback 时,在函数名后直接加括号,会立刻执行函数体,而不是等到显示/隐藏完成后才执行,而且只会 执行一次,只有当作回调参数的时候才会有多个元素而 执行多次
<div style="background-color:black;width:300px;height:300px;display:none;"></div>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
    $("div").fadeIn("slow","linear",function(){console.log("fadeIn 执行结束")});    //fadeIn 执行结束
    $("div").fadeOut("slow","linear",function(){console.log("fadeOut 执行结束")});  //fadeOut 执行结束
    $("div").fadeToggle("slow","linear",function(){console.log("fadeToggle 执行结束")});    //fadeToggle 执行结束
    $("div").fadeTo("slow",0.5,"linear",function(){console.log("fadeTo 方法的四个参数:执行速度、透明度、运动曲线、执行完毕后调用的回调函数")});
</script>
折叠动画
  • slideDown() - 下滑;
  • slideUp() - 上滑;
  • slideToggle() - 切换;
  • 底层以 display:none/block;opacity:[number]; 实现
  • 语法$(*selector*).slideToggle(*speed,timing-function,callback*);
  • 可选参数 speed:隐藏/显示的速度,可以三类值:slow、fast、毫秒
  • 可选参数 timing-function:运动曲线
  • 可选参数 callback:隐藏或显示完成后所执行的函数,如果调用 callback 时,在函数名后直接加括号,会立刻执行函数体,而不是等到显示/隐藏完成后才执行,而且只会 执行一次,只有当作回调参数的时候才会有多个元素而 执行多次
$("button").click(function () {
    $("div").slideToggle(function () {
        console.log("animate 动画结束")
    });
});
自定义动画
  • animate() 创建自定义动画
  • 语法$(*selector*).animate({params},speed,timing-function,callback);
  • 必需的 params 参数:定义形成动画的 CSS 属性
  • 可选的 speed 参数:规定效果的时长,取值:"slow"、"fast" 或毫秒
  • 可选的 timing-function 参数:运动曲线
  • 可选的 callback 参数:是动画完成后所执行的函数名称
  • 属性名:当使用 animate() 时,必须使用 Camel 标记法书写所有的属性名,比如,必须使用 paddingLeft 而不是 padding-left,使用 marginRight 而不是 margin-right,等等。
  • 颜色插件:色彩动画并不包含在核心 jQuery 库中,如需生成颜色动画,需要从 jquery.com 下载 颜色动画 插件。
  • 相对值:在值的前面加上 += 或 -=
  • 预定义值:把属性的动画值设置为 "show"、"hide" 或 "toggle"
//把 <div> 元素往右边移动 250px
$("div").animate({left:'250px'});

//动画使用多个属性
$("div").animate({ left:'250px', opacity:'0.5', height:'150px', width:'150px' });

//动画可以定义相对值(该值相对于元素的当前值)。需要在值的前面加上 += 或 -=
$("div").animate({ left:'250px', height:'+=150px', width:'+=150px' });

//预定义值:把属性的动画值设置为 "show"、"hide" 或 "toggle",就是 0 到 应有高度 之间切换
$("div").animate({ height:"toggle" });

// 动画队列
var div=$("div"); 
div.animate({height:'300px',opacity:'0.4'},"slow"); 
div.animate({width:'300px',opacity:'0.8'},"slow"); 
div.animate({height:'100px',opacity:'0.4'},"slow");
div.animate({width:'100px',opacity:'0.8'},"slow");
div.animate({left:'100px'},"slow"); 
div.animate({fontSize:'3em'},"slow");

// 链式调用
div.animate({ height: '300px', opacity: '0.4' }, "slow").
    animate({ width: '300px', opacity: '0.8' }, "slow").
    animate({ height: '100px', opacity: '0.4' }, "slow").
    animate({ width: '100px', opacity: '0.8' }, "slow").
    animate({ left: '100px' }, "slow").
    animate({ fontSize: '3em' }, "slow");
<button>animate</button>
<div style="background-color:black;width:100px;height:100px;"></div>
<script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript">
    $("button").click(function () {
        $("div").animate({
            width: "200px",
            height: "200px",
            "background-color": "pink",  //颜色无效
            transform: "rotate(45deg)",  //transform 无效
            display: "block"
        }, 1000, "linear", function () {
            console.log("animate 动画结束")
        });
    });
</script>
过程控制
  • stop() - 停止动画
  • finish() - 立即完成当前动画,并清除后续排队的动画,与 .stop(true, true) 方法类似
  • delay() - 延迟动画
  • 语法$(*selector*).stop(stopAll,goToEnd);
  • 可选 stopAll 参数:规定是否应该清除动画队列。默认是 false(仅停止活动的动画,允许任何排入队列的动画向后执行)
  • 可选 goToEnd 参数:规定是否立即完成当前动画。默认是 false(不立即完成)
  • 该方法,不同的是,finish() 也会引起所有排队动画的 CSS 属性停止。
$("#panel").stop();            //动画中途暂停,还可以继续执行
$("#panel").stop(true);        //队列中的动画都清除,动画中途结束,不可继续执行
$("#panel").stop(false,true);  //当前动画立即完成,后续队列不清除,还可继续执行

//stop() 停止动画 -> 可用于模拟防抖
ele.stop().toggle();

// 延迟下一个动画 2 秒
$("#panel").dalay(2000).animate({width:200px}, "fast")

事件操作

  • ready() - 规定当 DOM 完全加载时要执行的函数
  • on() - 绑定事件
  • off() - 解绑事件
  • one() - 一次性事件,触发后立即解绑
  • bind() - 1.7 版本被弃用
  • hover() - 元素集合.hover(移入时触发的函数, 移出时触发的函数) 如果只传一个函数,会在移入移出的时候都触发
  • trigger() - 手动触发事件,会触发默认行为,可以操作 jQuery 对象匹配的所有元素,没有返回值
  • triggerHandler() - 不会触发事件的默认行为,不会在 DOM 树中冒泡,只影响第一个匹配元素,返回最后一个事件处理函数的返回值,如果没有处理函数被执行,则返回undefined
  • $(this):JQuery 中 this 一般用 $(this) 代替,因为 this.innerHTML 跟 $(this).html() 的结果是一致的
  • 同时给多个元素绑定事件$(".brand-detail li,.size-detail li,.color-detail li").on( ... )
/* 绑定事件 on() */
//基础绑定
$("li").on("click",_=> console.log(_.target));
//事件委托绑定:第二个参数表示指定冒泡到哪一个节点(只触发这个节点)
$("ul").on("click","button",function(){ console.log(this); }); //this 的结果是 button节点
//事件参数多个:如果第二个往后的参数是字符串,则默认是冒泡触发事件的一个元素,如果传入的是对象,则认为是作为事件函数的参数
$("ul").on("click","button",{ result:"this is button" },function(e){
    console.log(e.data.result, this);
});  

/* 解绑事件 off() */
$("ul").off("click");  //移除 click 事件
$("ul").off();  //移除所有的事件
$("ul").off("input",funcA);     //解绑链式绑定中指定的一个

/* 触发一次 one() */
$("ul").one("click","button",{ result:"this is button" },function(e){
    console.log(e.data.result, this);
})

//链式绑定
$("button").click({arg:"something"},function(){
    console.log("clicked");
}).mouseout(function(){
    console.log("moved out");
})
$("input").on("input",function funcA(e){
    console.log($("input")[0].value);
}).on("input",{inputText:`${$("input")[0].value}`},function funcB(e){
    console.log(e.data.inputText);
});
//通过代码的形式自动触发事件
//在 1s 后触发点击事件
$("li").on("click", function(){
    console.log("click li");
})
setTimeout(() => {
    $("li").trigger("click");
}, 1000);

![trigger 方法.gif](./assets/jQuery/trigger 方法.gif)

Ajax 请求

$.ajax()
//高德API获取天气情况
$.ajax({
    url: "https://restapi.amap.com/v3/weather/weatherInfo",
    method: "get",
    data: {
        key: "429951164744f19f085244582f537008",
        city: "成都",
        extensions: "all"
    },
    dataType: "json",
    success: function (result, status, xhr) {
        result.forecasts[0].casts.forEach(item => {
            console.log(`${status} ${item.date} ${item.dayweather} ${item.daytemp}`)
            // success 2025-01-09 多云 12
            // success 2025-01-10 小雨 8
            // success 2025-01-11 多云 12
            // success 2025-01-12 阴 9
        })
    },
    error: function (xhr, status, error) {
        console.log(status);    //error
        console.log(error);     //Not Found
    }
})
$.get()

两种在客户端和服务器端进行请求-响应的常用方法是:GET 和 POST,具体区别见 详解

  • 语法$.get(URL,callback);$.get( URL, data, callback, dataType)
  • 参数 URL:请求的服务端地址
  • 可选参数 data:发送给服务器的字符串或 key/value 键值对对象
  • 可选参数 callback:请求成功后执行的回调函数;callback 参数包含三种:responseTxt - 结果内容;statusTXT - 调用状态;xhr - XMLHttpRequest 对象
  • 可选参数 dataType:从服务器返回的数据类型。例如:xml, json, script, html
$.get(
    "http://localhost:3000/list", 
    "name=James", 
    function(responseText,statusText) {
        console.log(responseText,statusText);
    },
    "html"
);
// 以html格式返回
$.get(
    "http://localhost:3000/list",
    {
        name: "James"
    },
    function(responseText,statusText) {
        console.log(responseText,statusText);
    },
    "json"
);
// 以json格式返回
$.post()
  • 语法$.post(URL,callback);$.post( URL, data, callback, dataType)
  • 参数 URL:请求的服务端地址
  • 可选参数 data:发送给服务器的字符串或 key/value 键值对对象
  • 可选参数 callback:请求成功后执行的回调函数;callback 参数包含三种:responseTxt - 结果内容;statusTXT - 调用状态;xhr - XMLHttpRequest 对象
  • 可选参数 dataType:从服务器返回的数据类型。例如:xml, json, script, html
$.post(
    "http://localhost:3000/list",
    "name=James",
    function(responseText,statusText) {
        console.log(responseText,statusText);
    },
    "html"
);
$.post(
    "http://localhost:3000/list",
    {name:"James"},
    function(responseText,statusText) {
        console.log(responseText,statusText);
    },
    "json"
);

钩子函数

ready()

所有的DOM节点加载完成后执行

//jQuery 入口函数
$(document).ready(function(){ /*...*/ })
$(function(){ /*...*/ })
// 这个入口函数是对 js 事件 DOMContentLoaded 的封装,但执行要比 ready 入口函数更快
document.addEventListener("DOMContentLoaded", function (event) {/*...*/})

//JavaScript 入口函数
window.onload = function(){ /*...*/ }
load()
  • load() - 从服务器 加载数据,并把返回的数据 放入被选元素
  • 语法 - $(selector).load(URL, data, callback);
  • 参数 URL - 地址、文件、锚点
  • 参数 data - 查询键/值字符串
  • 参数 callback - load() 方法完成后所执行的函数名称
  • callback 参数 包含三种:responseTxt - 结果内容;statusTXT - 调用状态;xhr - XMLHttpRequest 对象
// 地址
$("div").load("http://localhost:3000/list","name=James")
// 文件
$("div").load("test.html")
// 锚点
$("div").load("test.html#target")

//callback 参数
$("#div1").load("test.html",function(responseTxt,statusTxt,xhr){ 
    if(statusTxt=="success") alert("外部内容加载成功!"); 
    if(statusTxt=="error") alert("Error: "+xhr.status+": "+xhr.statusText); 
}); 
ajaxStart()

当前页面,第一个 Ajax 发送之前执行

ajaxSend()

每一个 Ajax 发送之前执行

ajaxSuccess()

每一个 Ajax 成功都会触发一次

ajaxError()

每一个 Ajax 失败后都会触发一次

ajaxComplete()

每一个 Ajax 执行完成后都会触发一次,不论成功还是失败

$(window).ajaxStart(function(){
    console.log("当前页面作用域,第一个Ajax发送之前执行");
    console.log("显示loading");
    $("#loading").css("display","block");
});
$(window).ajaxSend(function(){
    console.log("每一个Ajax发送之前执行");
    console.log("显示loading");
});
$(window).ajaxSuccess(function(){
    console.log("每一个Ajax成功都会触发一次");
    console.log("隐藏loading...(如果只有一个ajax请求)");
});
$(window).ajaxError(function(){
    console.log("每一个Ajax失败后都会触发一次");
});
$(window).ajaxComplete(function(){
    console.log("每一个Ajax执行完成后都会触发一次,不论成功还是失败");
})
ajaxStop()

所有 AJAX 请求已完成,当前页面作用域的最后一个Ajax成功

$(document).ajaxStop(function(){ 
        console.log("所有 AJAX 请求已完成,当前页面作用域的最后一个Ajax成功");
        console.log("隐藏loading");
        $("#loading").css("display","none");
    }
);`
when()

相当于 promise 的 finally 函数

var statusVal;
$.ajax({
    url: "http://localhost:3000/size",
    method: "get",
    dataType: "json",
    data: {},
    success: function (result, status) {
        result.forEach(item => {
            $(".brand-detail").append(`<li>${item.content}</li>`)
        });
        statusVal = status;
        console.log(statusVal);  //true
    }
})
// 根据 status 的状态 进行后续操作
function(){
  if(statusVal){  //false
    // 后续操作
}  
}
var statusVal;
var aj = $.ajax({
    url: "http://localhost:3000/size",
    method: "get",
    dataType: "json",
    data: {},
    success: function (result, status) {
        result.forEach(item => {
            $(".brand-detail").append(`<li>${item.content}</li>`)
        });
        statusVal = status;
        console.log(statusVal);  //true
    }
})

// 根据 statusVal 后续操作
function(){
  // myajax 请求完毕时执行
  $.when(aj).done(function(){
      if(statusVal){
        // 后续操作
      }  
    }) ;
}

插件

jq 对象的扩展

$.extend( [deep ], target, object1, ..., objectN ) 将一个或多个对象的内容合并到目标对象

  • deep:可选,Boolean 类型,是否深度合并对象,默认为 false
  • target:Object 类型,目标对象,其他对象的成员属性将被附加到该对象上。
  • object1:可选,Object 类型,第一个被合并的对象。
  • objectN:可选,Object 类型,第 N 个被合并的对象。
// 扩展一个单纯的方法
$.extend({
    lg(val) {
        console.log(val)
    }
})

$.lg("text content") // text content
// 扩展一个对象的属性
const obj1 = { name: "James" };
const obj2 = { age: 25 };

$.extend(obj1, obj2)
console.log(obj1); // {name: 'James', age: 25}
// 实现深拷贝
//    1. 如果第一个参数为true,表示深拷贝;
//    2. 从第二个参数(或第三个参数)开始,都属于原对象,将要拷贝给第一个参数(或第二个参数)
//    3. 如果是 undefined 则不会拷贝

//浅拷贝
$.extend(objCopy, objOri, { other:"other thing"} )
//深拷贝
$.extend(true, objCopy, objOri, { other:"other thing"} )
jq DOM 节点的扩展

$.fn.extend() 相当于给一个 DOM 元素扩展了一个方法或属性

<body>
    <button id="btn">test</button>
</body>
<script src="./jquery-3.7.1.min.js"></script>
<script>
    $.fn.extend({
        changeColor() {
            const $dom = $(this)
            $dom.css('background-color', 'red')
            setTimeout(() => {
                $dom.css('background-color', 'blue')
            }, 1000)
            return $(this) // 保证链式调用
        }
    })
    $('#btn').click(function (evt) {
        $(this).changeColor()
    })
</script>
// 拓展一个属性
$.fn.extend({
    setProperty(k, v) {
        $(this).attr(k, v)
        return $(this) // 保证链式调用
    }
})

$('#btn').click(function (evt) {
    $(this).setProperty("data-btn", "custom")
    console.log($(this).attr("data-btn")); // custom
})

示例

原生tab功能
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8" /> <title>优化版选项卡</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; list-style: none; } #container { width: 600px; margin: 30px auto; } .option, .content { border: 1px solid #000; text-align: center; font: bold 2rem/5rem Arial; } .option { display: flex; height: 80px; } .option li { flex: 1; cursor: pointer; } .content { height: 300px; border-top: none; } .content li { height: 100%; display: none; } .content li.active { display: block; } .option li.active, .option li:hover { background-color: #000; color: red; } </style> </head> <body> <div id="container"> <ul class="option"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> <ul class="content"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> </ul> </div> <script src="https://cdn.staticfile.org/jquery/3.7.1/jquery.min.js"></script> <script> $(function () { $.fn.extend({ tabs(defaultIndex) { var $options = $(this).find('.option li'); var $contents = $(this).find('.content li'); // 初始化选中 function setActive(index) { $options.eq(index).addClass('active').siblings().removeClass('active'); $contents.eq(index).addClass('active').siblings().removeClass('active'); } // 默认显示 setActive(defaultIndex || 0); // 点击切换 $options.on('click', function () { setActive($(this).index()); }); } }); // 初始化:默认显示第 2 个(从0开始) $('#container').tabs(2); }); </script> </body> </html>
Accordion 手风琴效果
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
</head>

<body>
    <nav class="container">
        <div class="title">
            <div class="slogon"></div>
            <p>web class</p>
            <i class="down">
                <narrow></narrow>
            </i>
        </div>
        <ul class="ul-1">
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
        </ul>
        <div class="title">
            <div class="slogon"></div>
            <p>web class</p>
            <i class="down">
                <narrow></narrow>
            </i>
        </div>
        <ul class="ul-2">
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
        </ul>
        <div class="title">
            <div class="slogon"></div>
            <p>web class</p>
            <i class="down">
                <narrow></narrow>
            </i>
        </div>
        <ul class="ul-3">
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
        </ul>
        <div class="title">
            <div class="slogon"></div>
            <p>web class</p>
            <i class="down">
                <narrow></narrow>
            </i>
        </div>
        <ul class="ul-4">
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
            <li>photoshop</li>
        </ul>
    </nav>
</body>
<script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>
<script>
    //suite mobile
    $(document).ready(function () {
        $("*").css({ margin: 0, padding: 0 });
        $("html").css("font-size", `${$("html")[0].clientWidth / 375 * 100}px`);
        //JQuery 实现手风琴效果
        $(".container").css({
            width: "80vw",
            height: "70vh",
            border: "1px solid black",
            margin: "10vh auto",
            overflow: "auto"
        })
        $(".container>div").css({
            display:"table",
            width: "100%",
            height: "5%",
            fontSize: "0.15em",
            cursor: "pointer"
        }).on("mouseover", function () {
            $(this).css("color", "red")
        }).on("mouseout", function () {
            $(this).css("color", "black")
        }).on("click", function () {
            $(this).siblings("ul").slideUp(500);                
            //箭头向上
            $(this).siblings("ul").prev().find("narrow").css({
                transition:"all 1s 0s ease-in",
                transform:"rotate(0deg)",
                transformOrigin:"center"
            });
            //点击的如果是同一元素,需要判断该元素是处于打开还是关闭的状态,否则,切换时将会处于反复打开的问题
            if ($(this).next("ul").css("display") == "none") {
                $(this).next("ul").slideDown(500);
                $(this).css("color", "red")
                console.log($(this).children("narrow"))
                //箭头向下
                $(this).find("narrow").css({
                    transition:"all 1s 0s ease-in",
                    transform:"rotate(-180deg)",
                    transformOrigin:"center"
                });
            } else {
                $(this).next("ul").slideUp(500);  //height变化时,display会自动设置成了block,等到变化结束,就会又回到none
                //箭头向上
                $(this).find("narrow").css({
                    transition:"all 1s 0s ease-in",
                    transform:"rotate(0deg)",
                    transformOrigin:"center"
                });
            }
        });
        $(".container .slogon").css({
            width:"5%",
            height: "100%",
            fontSize: "0.5em",
            display:"table-cell",
            textAlign: "center",
            verticalAlign:"middle"
        });
        $(".container p").css({
            width:"90%",
            height: "100%",
            fontSize: "0.5em",
            display:"table-cell",
            verticalAlign:"middle"
        });
        $(".container .down").css({
            width:"5%",
            height: "100%",
            display:"table-cell",            
            textAlign: "center",
            verticalAlign:"middle"
        })
        $(".container .down narrow").css({
            display: "inline-block",
            width:"0.05rem",
            height: "0.05rem",
            backgroundColor:"black",
            clipPath:"polygon(50% 0%, 100% 100%, 50% 50%,0 100%)"
        })
        $(".container>ul").css({
            width: "100%",
            listStyleType: "none",
            textIndent: "0.1rem",
            display:"none",
            backgroundColor: "black",
            color: "white"
        })
        $(".container li").css({
            height: "0.1rem",
            lineHeight: "0.1rem",
            fontSize: "0.05rem"
        }).on("mouseover", function () {
            $(this).css({
                backgroundColor: "white",
                color: "black"
            })
        }).on("mouseout", function () {
            $(this).css({
                backgroundColor: "black",
                color: "white"
            })
        })
    })
</script>
</html>

手风琴效果.png

购物车页面

<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1.0" />
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    body {
      background: #eeeeee
    }

    input[type="checkbox"] {
      appearance: none;
      width: 0.8rem;
      height: 0.8rem;
      background-color: white;
      margin: 0 0.5rem;
      text-align: center;
      line-height: 1rem;
      vertical-align: middle
    }

    input[type="checkbox"]:hover {
      background-color: #AF8875;
    }

    input[type="checkbox"]:checked {
      border: 1px solid #FDF7FF;
      background-color: #FDF7FF
    }

    input[type="checkbox"]:checked:after {
      content: "✓";
      color: purple;
      font-weight: bolder;
      font-size: 0.8rem;
    }

    .container {
      width: 80%;
      height: 100%;
      overflow: auto;
      margin: auto;
    }

    .container::-webkit-scrollbar {
      width: 3px;
      height: 8px;
      background: #A73B00;
    }

    .container::-webkit-scrollbar-thumb {
      background: #F9F871;
    }

    .title {
      width: 100%;
      height: 4rem;
      display: inline-table;
      position: sticky;
      top: 0;
      overflow: hidden;
      background: #69bff8;
      font-size: 0.8rem;
    }

    .title span {
      display: table-cell;
      vertical-align: middle;
      text-align: center
    }

    .title .title-chose {
      width: 10%;
      text-align: start
    }

    .title-info {
      width: 20%;
    }

    .product-info {
      width: 15%;
      padding: 0 2.5%
    }

    .title-describe {
      width: 20%
    }

    .product-describe {
      width: 18%;
      padding: 0 2%
    }

    .title-price,
    .product-price,
    .title-count,
    .product-count,
    .title-price-count,
    .product-price-count,
    .title-remove,
    .product-remove {
      width: 11%;
      font-size: 0.8rem
    }

    .store-title {
      width: 100%;
      height: 2rem;
      background: #C9F987
    }

    .store-title label,
    .store-title a {
      height: 100%;
      display: inline-block;
      font: bolder 0.8rem/2rem 宋体;
    }

    .store-title a {
      text-decoration: none;
      color: #892000;
    }

    .product-detail {
      padding-top: 0.5rem;
      font-size: 0;
    }

    .product-detail li {
      height: 15%;
      list-style-type: none;
      display: inline-block;
      vertical-align: middle;
      font-size: 0.8rem;
    }

    .product-img {
      width: 10%;
    }

    .product-img img {
      width: 100%;
      height: 100%;
    }

    .product-price,
    .product-count,
    .product-price-count,
    .product-remove {
      text-align: center;
    }

    .product-count input {
      vertical-align: top;
    }

    .product-count input[type="button"] {
      appearance: none;
      width: 2rem;
      height: 1.5rem;
      border: 0;
      background: #61DBE1;
      color: black;
    }

    .product-count input[type="button"]:hover {
      cursor: pointer
    }

    .product-count input[type="button"]:active {
      background: #F9F871;
      border: 1px solid #F9F871;
      color: white;
    }

    .product-count input[type="number"] {
      outline: none;
      width: 3rem;
      height: 1.5rem;
      text-align: center;
      border: 0
    }

    .product-count input[type="number"]::-webkit-inner-spin-button,
    .product-count input[type="number"]::-webkit-outer-spin-button {
      appearance: none;
    }

    .product-price-count {
      color: red
    }

    .product-remove:hover {
      color: red;
      cursor: pointer
    }

    .total {
      width: 80%;
      height: 3rem;
      position: absolute;
      bottom: 0;
      background: white;
      overflow: auto;
      text-align: right;
    }

    .total div,
    .total button {
      height: 100%;
      border: 0;
      float: left;
      color: white;
      font: bolder 1.2rem 宋体;
    }

    .total div {
      width: 78%;
      background: #BFC1FF;
      line-height: 3rem;
      padding-right: 2%
    }

    .total button {
      width: 20%;
      border: 0;
      background: #898CC9;
    }
  </style>
</head>

<body>
  <div class="container">
    <section class="title">
      <span class="title-chose"><input id="chose-all" type="checkbox" /><label for="chose-all">全选</label></span>
      <span class="title-info">商品信息</span>
      <span class="title-describe">商品参数</span>
      <span class="title-price">单价</span>
      <span class="title-count">数量</span>
      <span class="title-price-count">金额</span>
      <span class="title-remove">操作</span>
    </section>
    <section class="cart">
      <div class="store-title">
        <input id="stores" class="store-check" type="checkbox" /><label for="stores" class="store-name">店铺:</label>
        <a href="#">null</a>
      </div>
      <ul class="product-detail">
        <li class="product-check"><input type="checkbox" /></li>
        <li class="product-img"><img src="" alt="商品图片"></li>
        <li class="product-info"><span></span></li>
        <li class="product-describe">规格:<span></span><br>尺寸:<span></span></li>
        <li class="product-price">$<span></span></li>
        <li class="product-count"><input type="button" value="-" /><input type="number" value="1" /><input type="button"
            value="+" /></li>
        <li class="product-price-count">$<span></span></li>
        <li class="product-remove"><span>移除商品</span></li>
        <div style="clear:both;"></div>
      </ul>
    </section>
    <section class="total">
      <div>总计:<span></span></div>
      <button>结算</button>
    </section>
  </div>
</body>
<script>
  // mock data
  Mock.mock("http://localhost:3000/shoppingcart", {
    "shoppingcart|3-5": [
      {
        "store": "店铺@integer(1,100)",
        "products|1-5": [
          {
            "name": "商品@integer(1,100)",
            "describe-specs": "规格@integer(1,100)",
            "describe-size": "尺寸@integer(1,100)",
            "price|1-100.2": 1,
            "counts|1-10": 1,
            "url": "https://picsum.photos/200/300?random=@integer(1,100)"
          }
        ]
      }
    ]
  });
  //suit for mobile device
  $("html").css("font-size", `${$("html").innerWidth / 375 * 12}px`);
  //render data to page
  function renderData(resultData) {
    //link Elements
    var cart = $(".cart").attr("data-index", "0");
    //add new element shop
    for (let j = resultData.length - 1; j >= 0; j--) {
      j != 0 ? cart.after(cart.clone().attr("data-index", `${j}`)) : null;
      // for(let i = resultData[j].products.length; i > 0; i--) {
      //     //mistake: (---below---)
      //     //if there isn't slice() to locate the productDetail , it will add more than expected numbers
      //     // i != 0 ? productDetail.slice(resultData[j].products.length).after(productDetail.clone()).attr("data",mk) : null;
      // }
    }
    //add new element products in terms of added shop elements
    var productDetail = $(".product-detail");
    for (let j = resultData.length - 1; j >= 0; j--) {
      for (let i = resultData[j].products.length - 1; i >= 0; i--) {
        i != 0 ? productDetail.eq(j).after(productDetail.eq(j).clone()) : null;
      }
    }
    //link Elements again after added elements
    var cart = $(".cart");
    var productDetail = $(".product-detail");
    var titleCheck = $(".title-chose input");
    var shopCheck = $(".store-check");
    var shopCheckLabel = $(".store-check~label");
    var shopName = $(".store-title a");
    var eachCheck = $(".product-check input");
    var img = $(".product-img img");
    var info = $(".product-info span");
    var describeSpecs = $(".product-describe span:first-of-type");
    var describeSize = $(".product-describe span:last-of-type");
    var price = $(".product-price span");
    var decrease = $(".product-count").children("[type='button']:even");
    var increase = $(".product-count").children("[type='button']:odd");
    var counts = $(".product-count").children("[type='number']");
    var countsPrice = $(".product-price-count span");
    var productRemove = $(".product-remove");
    var totalPrice = $(".total span");
    //get data from ajax and set data-index attribute for event
    let m = 0; //index of elements
    for (let j = 0; j < resultData.length; j++) {
      shopName.eq(j).text(resultData[j].store); //get shop name
      shopCheck.eq(j).attr({ "data-index": `${j}`, "id": `${j}` });
      shopCheckLabel.eq(j).attr("for", `${j}`);
      for (let i = 0; i < resultData[j].products.length; i++) {
        if (m < productDetail.length) {     //in terms of the productDetail elemnts' numbers
          //get data
          img.eq(m).attr("src", resultData[j].products[i].url); //get productImg
          info.eq(m).text(resultData[j].products[i].name); //get product information
          describeSpecs.eq(m).text(resultData[j].products[i]["describe-specs"]);
          describeSize.eq(m).text(resultData[j].products[i]["describe-size"]);  //get product describe
          price.eq(m).text(resultData[j].products[i].price);  //get count price
          counts.eq(m).val(resultData[j].products[i].counts);
          countsPrice.eq(m).text((parseFloat(counts.eq(m).val() * parseFloat(price.eq(m).text()))).toFixed(2));
          console.log(parseFloat(price.eq(m).text()))
          //set index
          eachCheck.eq(m).attr("data-index", `${j}-${i}`);  //set check index
          price.eq(m).attr("data-index", `${j}-${i}`);  //set price index
          decrease.eq(m).attr("data-index", `${j}-${i}`);
          increase.eq(m).attr("data-index", `${j}-${i}`);  //set - + input index
          counts.eq(m).attr("data-index", `${j}-${i}`);  //set count index
          countsPrice.eq(m).attr("data-index", `${j}-${i}`);  //set counts' summary price
          productRemove.eq(m).attr("data-index", `${j}-${i}`);     //set remove operator index
          productDetail.eq(m).attr("data-index", `${j}-${i}`);  //set each productDetail index
          m++;
        } // else none
      }
    }
    function countTotalPrice() {
      let countsPriceTotal = 0;
      let countsPrice = $(".product-price-count span");
      for (let i = 0; i < countsPrice.length; i++) {
        countsPriceTotal += parseFloat(countsPrice.eq(i).text());
      }
      totalPrice.text(countsPriceTotal.toFixed(2));
    };
    countTotalPrice();
    return { titleCheck, shopCheck, eachCheck, img, info, describeSpecs, describeSize, price, decrease, increase, counts, countsPrice, productRemove, productDetail, cart, countTotalPrice };
  }
  //check products
  function checkProducts(titleCheck, shopCheck, eachCheck) {
    function toggleAll() {
      for (let j = 0; j < shopCheck.length; j++) {
        if (!shopCheck.eq(j).prop("checked")) {
          titleCheck.prop("checked", false);
          break;  //avoid continuous circle that leads to cover the value
        } else if (shopCheck.eq(j).prop("checked")) {
          titleCheck.prop("checked", true);
        }
      }
    }
    eachCheck.on("click", function () {
      var curLineIndex = $(this).attr("data-index").substring(0, 1);
      var curEachChecks = eachCheck.filter(`[data-index^='${curLineIndex}']`);
      var curLineShop = shopCheck.filter(`[data-index='${curLineIndex}']`);
      for (let i = 0; i < curEachChecks.length; i++) {
        if (!curEachChecks.eq(i).prop("checked")) {
          curLineShop.prop("checked", false);
          toggleAll();
          break;  //avoid continuous circle that leads to cover the value
        } else {
          curLineShop.prop("checked", true);
          toggleAll();
        }
      }
    });
    shopCheck.on("click", function () {
      var curLineIndex = $(this).attr("data-index");
      var curEachChecks = eachCheck.filter(`[data-index^='${curLineIndex}']`);
      for (let i = 0; i < curEachChecks.length; i++) {
        if ($(this).prop("checked")) {
          curEachChecks.eq(i).prop("checked", true);
        } else {
          curEachChecks.eq(i).prop("checked", false);
        }
      }
      toggleAll();
    });
    titleCheck.on("click", function () {
      if ($(this).prop("checked")) {
        eachCheck.prop("checked", true);
        shopCheck.prop("checked", true);
      } else {
        eachCheck.prop("checked", false);
        shopCheck.prop("checked", false);
      };
    });
  }
  //summary price
  function changeNum(price, decrease, increase, counts, countsPrice, productDetail, countTotalPrice) {
    decrease.on("click", function () {
      for (let i = 0; i < counts.length; i++) {
        if ($(this).attr("data-index") === counts.slice(i).attr("data-index")) {
          var curCount = counts.filter(`[data-index='${counts.slice(i).attr("data-index")}']`);
          var afterCount = parseInt(curCount.val()) - 1;
          var curPrice = price.filter(`[data-index='${price.slice(i).attr("data-index")}']`);
          var curCountsPrice = countsPrice.filter(`[data-index='${countsPrice.slice(i).attr("data-index")}']`);
          if (parseInt(afterCount) !== 0) {
            //decrease the input’s number
            curCount.val(afterCount);
            curCountsPrice.text((parseFloat(curPrice.text()) * afterCount).toFixed(2));
          } else {
            curCountsPrice.text((parseFloat(curPrice.text()) * parseInt(curCount.val(1).val())).toFixed(2));
          }
        } //else none
      }
    });
    increase.on("click", function () {
      for (let i = 0; i < counts.length; i++) {
        if ($(this).attr("data-index") === counts.slice(i).attr("data-index")) {
          var curCount = counts.filter(`[data-index='${counts.slice(i).attr("data-index")}']`);
          var afterCount = parseInt(curCount.val()) + 1;
          var curPrice = price.filter(`[data-index='${price.slice(i).attr("data-index")}']`);
          var curCountsPrice = countsPrice.filter(`[data-index='${countsPrice.slice(i).attr("data-index")}']`);
          //increase the input’s number
          curCount.val(afterCount);
          curCountsPrice.text((parseFloat(curPrice.text()) * afterCount).toFixed(2));
        } //else none
      }
    });
    counts.on("blur", function () {
      for (let i = 0; i < price.length; i++) {
        if ($(this).attr("data-index") === price.slice(i).attr("data-index")) {
          var curCount = counts.filter(`[data-index='${counts.slice(i).attr("data-index")}']`)
          var curPrice = price.filter(`[data-index='${price.slice(i).attr("data-index")}']`);
          var curCountsPrice = countsPrice.filter(`[data-index='${countsPrice.slice(i).attr("data-index")}']`);
          if (parseInt(curCount.val()) <= 0 || isNaN(parseInt(curCount.val()))) {
            curCountsPrice.text(parseInt(curCount.val(1).val()) * parseFloat(curPrice.text()));
          } else {
            curCount.val(parseInt(curCount.val()));  //change producr numbers directly from the input,ensure the value is integer
            curCountsPrice.text(parseInt(curCount.val()) * parseFloat(curPrice.text()));
          }
        }
      }
    });
    productDetail.on("click", function () {
      countTotalPrice();
    })
  }
  //operate remove each line product
  function eachRemove(productRemove, productDetail, cart, totalPrice) {
    productRemove.on("click", function () {
      productDetail.filter(`[data-index='${$(this).attr("data-index")}']`).remove();
      cart.filter(":not(:has(.product-detail))").remove();
      let countsPriceTotal = 0;
      let afterRemovePriceCount = $(".product-price-count span");
      for (let i = 0; i < afterRemovePriceCount.length; i++) {
        countsPriceTotal += parseFloat(afterRemovePriceCount.eq(i).text());
      }
      $(".total span").text(countsPriceTotal.toFixed(2));

    })
  }

  //ajax function
  $.ajax({
    url: "http://localhost:3000/shoppingcart",
    methos: "get",
    data: {},
    dataType: "json",
    success: function (result, status) {
      console.log(result);
      let render = renderData(result.shoppingcart);
      changeNum(render.price, render.decrease, render.increase, render.counts, render.countsPrice, render.productDetail, render.countTotalPrice)
      checkProducts(render.titleCheck, render.shopCheck, render.eachCheck);
      eachRemove(render.productRemove, render.productDetail, render.cart, render.totalPrice);
    },
    error: function (xhr, status, error) {
      console.log(status);
      console.log(error);
    }
  });
</script>

</html>

image.png