豆瓣电影:SPA

2018-12-02  本文已影响0人  前端小木鱼

一、项目介绍

使用原生JS和Jquery做了一个移动端豆瓣电影单页面应用,通过点击切换Top250、北美排行榜、电影搜索三个面板。
Top250面板实现了触底懒加载数据,使用Ajax新增列表项目,数据加载中有Loading动画提示,所有数据加载完后显示底部盒子,点击可回到顶部。电影搜索面板通过<input>标签接收用户搜索内容,通过click()、keyup()方法实现单击搜索按钮或敲打enter键触发事件函数,使用Ajax发送请求处理数据。
使用html模板利用jQuery(html)创建DOM元素,提取公共方法提高了代码简洁性和可复用性。

二、所涉知识

(一)自适应网页设计

在网页代码的头部,加入一行viewport元标签。
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
参考:自适应网页设计

(二)JavaScript

(1)join()

join()方法以指定参数作为分隔符,将所有数组成员连接为一个字符串返回。如果不提供参数,默认用逗号分隔。
$node.find('.introduction .geners').text(movie.genres.join('、'));

(2)map()

map方法将数组的所有成员依次传入参数函数,然后把每一次的执行结果组成一个新数组返回。
$node.find('.introduction .directors').text(movie.directors.map(v => v.name).join('、'));

(3)forEach()

forEach方法与map方法很相似,也是对数组的所有成员依次执行参数函数。但是,forEach方法不返回值,只用来操作数据。

data.subjects.forEach((movie) => {
  movie = movie.subject ? movie.subject : movie; 
  this.$ul.append(utils.createElementItem(movie));
});

(4)setTimeout()

setTimeout函数用来指定某个函数或某段代码,在多少毫秒之后执行。它返回一个整数,表示定时器的编号,以后可以用来取消这个定时器。

_this.clock = setTimeout(function(){
  if (_this.$container.height() - 400 <= _this.$top250.height() + _this.$top250.scrollTop()) {
    _this.renderData();
  }
},100)

(5)clearTimeout()

setTimeou返回一个整数值,表示计数器编号。将该整数传入clearTimeout函数,就可以取消对应的定时器。
if (_this.clock) clearTimeout(_this.clock);

(三)jQuery

1.jQuery.ajax( [settings ] )

执行一个异步的HTTP(Ajax)的请求。

常用参数:

url (默认: 当前页面地址),类型: String,发送请求的地址。
type (默认: 'GET'),类型: String,请求方式 ("POST" 或 "GET")。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。
data,类型: [Object, String],发送到服务器的数据,对象必须为"{键:值}"格式。
dataType,类型: String,预期服务器返回的数据类型。

jqXHR 对象:

$.ajax() 返回XMLHttpRequest(jqXHR)对象,该对象是浏览器的原生的XMLHttpRequest对象的一个超集。
从 jQuery 1.5 开始,$.ajax()返回的jqXHR对象 实现了 Promise 接口, 使它拥有了 Promise 的所有属性,方法和行为。
this在所有的回调中的引用,是这个对象在传递给$.ajax的设置中上下文;如果没有指定context(上下文),this 引用的是Ajax设置的本身。

    $.ajax({
            url: 'https://api.douban.com/v2/movie/top250',
            data: {
                start: _this.index,
                count: _this.count,
            },
            dataType: 'jsonp',
        }).done((ret) => {
            this.index += this.count;
            if (this.index > ret.total ) this.isOver = true;
            this.setData(ret);
        }).fail(function(){
            alert('获取数据失败');
        }).always(() => {
            this.isLoading = false;
            this.$iconLoading.css('display','none');
        });

2. jQuery()

CSS选择器作为参数

jQuery()本质上是一个构造函数,主要作用是返回jQuery对象的实例,简写为$()。比如,var listItems = $("li");表面上是选中li元素,实际上是返回对应于li元素的jQuery实例。因为只有这样,才能在DOM对象上使用jQuery提供的各种方法。
$node.find('a').attr('href',movie.alt);

HTML字符串作为参数

如果直接在jQuery构造函数中输入HTML字符串,返回的也是jQuery实例。它与从CSS选择器生成的jQuery实例完全一样,唯一的区别就是,它对应的DOM结构不属于当前文档。

var tpl =
  `<div class="iconfont icon-loading"></div> 
   <div class="bottom">
     <a class="returnTop" href="#">回到顶部</a>
   </div> `
this.$container.append($(tpl));

3.jQuery构造函数返回的结果集

jQuery的核心思想是“先选中某些网页元素,然后对其进行某种处理”(find something, do something),也就是说,先选择后处理,这是jQuery的基本操作模式。所以,绝大多数jQuery操作都是从选择器开始的,返回一个选中的结果集。

(1)index()

index() 方法返回指定元素相对于其他指定元素的 index 位置。这些元素可通过 jQuery 选择器或 DOM 元素来指定。
获得第一个匹配元素相对于其同胞元素的 index 位置:$(selector).index()
获得元素相对于选择器的 index 位置:$(selector).index(element)
_this.$panels.eq($(this).index()).show().siblings().hide();

(2)eq()

如果想要在结果集取出一个jQuery对象的实例,不需要取出DOM对象,则使用eq方法,它的参数是实例在结果集中的位置(从0开始)。
_this.$panels.eq($(this).index()).show().siblings().hide();

4. 结果集的过滤方法

选择器选出一组符合条件的网页元素以后,jQuery提供了许多方法,可以过滤结果集,返回更准确的目标。

(1)siblings(),nextAll(),prevAll()

siblings方法返回当前元素的所有同级元素,nextAll方法返回当前元素其后的所有同级元素,prevAll方法返回当前元素前面的所有同级元素。
$(this).addClass('active').siblings().removeClass('active');

(2)closest(),find()

closest方法返回当前元素,以及当前元素的所有上级元素之中,第一个符合条件的元素。find方法返回当前元素的所有符合条件的下级元素。
$node.find('a').attr('href',movie.alt);

5. DOM相关方法

许多方法可以对DOM元素进行处理。

(1)html()和text()

html()返回该元素包含的HTML代码,text()返回该元素包含的文本。jQuery的许多方法都是取值器(getter)与赋值器(setter)的合一,即取值和赋值都是同一个方法,不使用参数的时候为取值器,使用参数的时候为赋值器。
html()和text()如果没有参数,就会当作取值器使用,取回结果集的第一个元素所包含的内容。如果对这两个方法提供参数,就是当作赋值器使用,修改结果集所有成员的内容,并返回原来的结果集,以便进行链式操作。
$node.find('.introduction .title').text(movie.title);

(2)addClass(),removeClass(),toggleClass()

addClass()用于添加一个类,removeClass()用于移除一个类,toggleClass()用于切换一个类(如果无就添加,如果有就移除)。
$(this).addClass('active').siblings().removeClass('active');

(3)css()

css()用于获取、改变css的值。css()的参数是css属性名,CSS属性名的CSS写法和DOM写法都可以接受,比如font-size和fontSize都行。
$panel.find('.bottom').css('display','block');

(4)val()

val()返回结果集第一个元素的值,或者设置当前结果集所有元素的值。
_this.keyword = _this.$input.val();

(5)attr()和prop()

首先,这里要区分两种属性。一种是网页元素的属性,比如a元素的href属性、img元素的src属性,这要使用attr()读写。另一种是DOM元素的属性,比如tagName、nodeName、nodeType等等,这要使用prop()读写。
<input type="checkbox" checked="checked" />
对于checked属性,attr()读到的是checked,prop()读到的是true。可以看到,attr()读取的是网页上该属性的值,而prop()读取的是DOM元素的该属性的值,根据规范,element.checked应该返回一个布尔值。所以,判断单选框是否选中,要使用prop()

$node.find('a').attr('href',movie.alt);
$node.find('.cover img').attr('src',movie.images.small);
(6)removeAttr()和removeProp()

removeAttr()移除某个HTML属性,removeProp()移除某个DOM属性。

(7)empty()

empty() 从被选元素移除所有内容,包括所有文本和子节点。
_this.$ul.empty();

(8)data()

data()用于在一个DOM对象上储存数据。

6.添加、复制和移动网页元素的方法

jQuery方法提供一系列方法,可以改变元素在文档中的位置。

(1)append方法,appendTo方法

append方法将参数中的元素插入当前元素的尾部,appendTo方法将当前元素插入参数中的元素尾部。
this.$container.append($(tpl));

(2)prepend方法,prependTo方法

prepend方法将参数中的元素,变为当前元素的第一个子元素。如果prepend方法的参数不是新生成的元素,而是当前页面已存在的元素,则会产生移动元素的效果。
prependTo方法将当前元素变为参数中的元素的第一个子元素。

(3)after方法,insertAfter方法

after方法将参数中的元素插在当前元素后面,insertAfter方法将当前元素插在参数中的元素后面。

(4)before方法,insertBefore方法

before方法将参数中的元素插在当前元素的前面,insertBefore方法将当前元素插在参数中的元素的前面。

(5)wrap方法,wrapAll方法,unwrap方法,wrapInner方法

wrap方法将参数中的元素变成当前元素的父元素,wrap方法的参数还可以是一个函数。wrapAll方法为结果集的所有元素,添加一个共同的父元素。unwrap方法移除当前元素的父元素。wrapInner方法为当前元素的所有子元素,添加一个父元素。

(6)clone方法

clone方法克隆当前元素。对于那些有id属性的节点,clone方法会连id属性一起克隆。所以,要把克隆的节点插入文档的时候,务必要修改或移除id属性。

(7)remove方法,detach方法,replaceWith方法

remove方法移除并返回一个元素,取消该元素上所有事件的绑定。detach方法也是移除并返回一个元素,但是不取消该元素上所有事件的绑定。replaceWith方法用参数中的元素,替换并返回当前元素,取消当前元素的所有事件的绑定。

7. 动画效果方法

jQuery提供一些方法,可以很容易地显示网页动画效果。但是,总体上来说,它们不如CSS动画强大和节省资源,所以应该优先考虑使用CSS动画。
如果将jQuery.fx.off设为true,就可以将所有动画效果关闭,使得网页元素的各种变化一步到位,没有中间过渡的动画效果。

动画效果的简便方法

jQuery提供以下一些动画效果方法。
show:显示当前元素。
hide:隐藏当前元素。
toggle:显示或隐藏当前元素。
_this.$panels.eq($(this).index()).show().siblings().hide();

7. 事件处理

(1)事件绑定的简便方法

jQuery提供一系列方法,允许直接为常见事件绑定回调函数:
click,keypress,keyup,keyup,mouseover,mouseout,mouseenter,mouseleave,hover,scroll,focus,blur,resize
如果不带参数调用这些方法,就是触发相应的事件,从而引发回调函数的运行。需要注意的是,通过这种方法触发回调函数,将不会引发浏览器对该事件的默认行为。比如,对a元素调用click方法,将只触发事先绑定的回调函数,而不会导致浏览器将页面导向href属性指定的网址。
hover方法需要特别说明,它接受两个回调函数作为参数,分别代表mouseenter和mouseleave事件的回调函数。
注意,在回调函数内部,this关键字指的是发生该事件的DOM对象。为了使用jQuery提供的方法,必须将DOM对象转为jQuery对象,因此写成$(this)。

$panel.find('.returnTop').click(function(){
  $panel.scrollTop(0);
});
this.$input.keyup(function(e){
  if(e.keyCode === 13) {
    _this.$button.click();
  };
});
this.$top250.scroll(function(){
  if (_this.clock) clearTimeout(_this.clock);
  _this.clock = setTimeout(function(){
    if (_this.$container.height() - 400 <= _this.$top250.height() + _this.$top250.scrollTop()) {
      _this.renderData();
    }
  },100)
});
(2)on方法,trigger方法,off方法

除了简便方法,jQuery还提供事件处理的通用方法。

on方法是jQuery事件绑定的统一接口。

事件绑定的那些简便方法,其实都是on方法的简写形式。on方法接受两个参数,第一个是事件名称,第二个是回调函数。
on方法允许一次为多个事件指定同样的回调函数。

$('input[type="text"]').on('focus blur', function (){
  console.log('focus or blur');
});

on方法还可以为当前元素的某一个子元素,添加回调函数。
以下代码为ul的子元素li绑定click事件的回调函数。采用这种写法时,on方法接受三个参数,子元素选择器作为第二个参数,夹在事件名称和回调函数之间。
这种写法有两个好处。首先,click事件还是在ul元素上触发回调函数,但是会检查event对象的target属性是否为li子元素,如果为true,再调用回调函数。这样就比为li元素一一绑定回调函数,节省了内存空间。其次,这种绑定的回调函数,对于在绑定后生成的li元素依然有效。

$('ul').on('click', 'li', function (e){
  console.log(this);
});

on方法还允许向回调函数传入数据。
以下代码在发生click事件之后,会通过event对象的data属性,在控制台打印出所传入的数据(即“张三”)。

$("ul" ).on("click", {name: "张三"}, function (event){
    console.log(event.data.name);
});
trigger方法

trigger方法用于触发回调函数,它的参数就是事件的名称。以下代码触发li元素的click事件回调函数。与那些简便方法一样,trigger方法只触发回调函数,而不会引发浏览器的默认行为。
$('li').trigger('click')

off方法

off方法用于移除事件的回调函数,以下代码移除li元素所有的click事件回调函数。
$('li').off('click')

(3)event对象

当回调函数被触发后,它们的参数通常是一个事件对象event。以下代码的回调函数的参数e,就代表事件对象event。

$(document).on('click', function (e){
    // ...
});

event对象有以下属性:
type:事件类型,比如click。
which:触发该事件的鼠标按钮或键盘的键。
target:事件发生的初始对象。
data:传入事件对象的数据。
pageX:事件发生时,鼠标位置的水平坐标(相对于页面左上角)。
pageY:事件发生时,鼠标位置的垂直坐标(相对于页面左上角)。
event对象有以下方法:
preventDefault:取消浏览器默认行为。
stopPropagation:阻止事件向上层元素传播。

(四)ES6

箭头函数(arrow)

(1)定义:

ES6允许使用“箭头”(=>)定义函数。如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

(2)实例:回调函数的简化

箭头函数的一个用处是简化回调函数。

(3)注意点:
data.subjects.forEach((movie) => {
  movie = movie.subject ? movie.subject : movie; 
  this.$ul.append(utils.createElementItem(movie));
});

(五)CSS3

CSS Animation

(1)基本用法

首先,CSS Animation需要指定动画一个周期持续的时间,以及动画效果的名称。

div:hover {
  animation: 1s rainbow;
}

上面代码表示,当鼠标悬停在div元素上时,会产生名为rainbow的动画效果,持续时间为1秒。为此,我们还需要用keyframes关键字,定义rainbow效果。

@keyframes rainbow {
  0% { background: #c00; }
  50% { background: orange; }
  100% { background: yellowgreen; }
}
(2)animation的各项属性

animation 属性是一个简写属性,用于设置六个动画属性:
animation: name duration timing-function delay iteration-count direction;

属性 描述
animation-name 规定需要绑定到选择器的 keyframe 名称。
animation-duration 规定完成动画所花费的时间,以秒或毫秒计。
animation-timing-function 规定动画的速度曲线。
animation-delay 规定在动画开始之前的延迟。
animation-iteration-count 规定动画应该播放的次数。
animation-direction 规定是否应该轮流反向播放动画。

注释:请始终规定 animation-duration 属性,否则时长为 0,就不会播放动画了。

main section .container .icon-loading:before {
    display: inline-block;
    font-size: 34px;
    animation: rotate 1s linear infinite;
}
@keyframes rotate {
    0% {transform: rotate(0deg);}
    100% {transform: rotate(360deg);}
}

三、重要方法

(一)公用方法

//准备提示节点
preparePrompt: function() {
    var tpl =
           `<div class="iconfont icon-loading"></div> 
            <div class="bottom">
                <a class="returnTop" href="#">回到顶部</a>
            </div> `
    this.$container.append($(tpl));
},
//创建并设置电影节点
createElementItem: function(movie){
    var tpl = 
            `<li class="item">
                <a href="">
                    <div class="cover">
                        <img src="">
                    </div>
                    <div class="introduction">
                        <h3 class="title"></h3>
                        <p><span class="score"> </span> 分 / <span class="collect"></span> 收藏</p>
                        <p><span class="year"></span> / <span class="geners"></span></p>
                        <p>导演:<span class="directors"></span></p>
                        <p>主演:<span class="casts"></span></p>
                    </div>
                </a>
            </li>`
    var $node = $(tpl);
    $node.find('a').attr('href',movie.alt);
    $node.find('.cover img').attr('src',movie.images.small);
    $node.find('.introduction .title').text(movie.title);
    $node.find('.introduction .score').text(movie.rating.average);
    $node.find('.introduction .collect').text(movie.collect_count);
    $node.find('.introduction .year').text(movie.year);
    $node.find('.introduction .geners').text(movie.genres.join('、'));
    $node.find('.introduction .directors').text(movie.directors.map(v => v.name).join('、'));
    $node.find('.introduction .casts').text(movie.casts.map(v => v.name).join('、'));
    return $node;
},
//设置数据
setData: function(data){
    data.subjects.forEach((movie) => {
        movie = movie.subject ? movie.subject : movie; 
        this.$ul.append(utils.createElementItem(movie));
    });
},
//展示底部
showBottom: function($panel){
    $panel.find('.bottom').css('display','block');
},
//回到顶部
returnTop: function($panel) {
    $panel.find('.returnTop').click(function(){
        $panel.scrollTop(0);
    });
},

(二)TOP250方法

//处理触底加载
this.$top250.scroll(function(){
    if (_this.clock) clearTimeout(_this.clock);
    _this.clock = setTimeout(function(){
        if (_this.$container.height() - 400 <= _this.$top250.height() + _this.$top250.scrollTop()) {
            _this.renderData();
        }
    },100)
});
//获取数据
getData: function(){
    var _this = this;
    this.$iconLoading = this.$top250.find('.icon-loading');
    this.isLoading = true;
    this.$iconLoading.css('display','block');
    //发送请求
    $.ajax({
        url: 'https://api.douban.com/v2/movie/top250',
        data: {
            start: _this.index,
            count: _this.count,
        },
        dataType: 'jsonp',
    }).done((ret) => {
        this.index += this.count;
        if (this.index > ret.total ) this.isOver = true;
        this.setData(ret);
    }).fail(function(){
        alert('获取数据失败');
    }).always(() => {
        this.isLoading = false;
        this.$iconLoading.css('display','none');
    });
},

(三)搜索方法

//获取数据
getData: function(callback){
    if (this.isLoading) return;
    var _this = this;
    this.$iconLoading = this.$search.find('.icon-loading');
    this.$iconLoading.css('display','block');
    this.isLoading = true;
        $.ajax({
        url: 'https://api.douban.com/v2/movie/search',
        data: {
            q: _this.keyword
        },
        dataType: 'jsonp',
    }).done((ret) => {
        this.setData(ret);
        callback(this.$search);
    }).fail(function(){
        alert('获取数据失败');
    }).always(() => {
        this.isLoading = false;
        this.$iconLoading.css('display','none');
    })  
},

(四)面板方法

//处理tab切换
bind: function(){
    let _this = this;
    this.$tabs.click(function() {
        $(this).addClass('active').siblings().removeClass('active');
        _this.$panels.eq($(this).index()).show().siblings().hide();
    }); 
},

源码地址
预览地址

上一篇 下一篇

猜你喜欢

热点阅读