一个后端的前端学习之旅——4.第一个demo上线以及关于前端框架我的看法
2016-02-16 18:10
921 查看
作为一个后端学习前端,我决的主要应该讲解学习的方法,而不是具体的技巧
菊花转起来
打开时瀑布流默认在右边有个很大的留白(估计是 freewall 我没用对)
懒加载加了但是貌似没效果
demo 美国vps 图慢 可能被墙 能不能看随缘
源码 tag为2016-02-16-demo。预警: bower npm的东西我都放到git中了(被坑过),比较大,
做什么呢,我扒了扒之前收藏夹里面的东西 codrops 上刚好看到两个有点意思的东西
全屏video播放
一个有意思的鼠标经过hover
第二个东西启发我想到了拉钩 上面如下图位置的鼠标hover效果,然后我就想这么多有图的知识点为毛不做个照片墙,鼠标移动过去标题出现就用拉钩的这个效果,点进去开始播视频,好像有点意思。
获得知识点tag: http://www.xuetangx.com/api/v2/fragment/tags
根据tag id获得对应知识点列表: http://www.xuetangx.com/api/v2/fragment/tag/1/
gulp 和 spine 我分别在上面两个帖子中讲过 先搞定gulp 喜闻乐见的跨域问题,有些东西就不说了。 尤其是model获得数据这里我就不再赘述。
先看下最后的目录结构, course有关的东西都没用,之前留下的先留着。
根据上个帖子讲的,准备tag和知识点的两个model
上一个帖子没有提到的东西
而不是
那么怎么做呢,两种选择,看文档或者看源码,个人感觉spine的文档很烂,然而他源码很短,所以当时我直接看的源码而没看文档。
思考流程,这是个ajax操作,肯定在ajax.coffee里面,然后要拿数据肯定已经success了,搜success,两处搜索结果,都在不同的两个recordResponse中,其中这行代码告诉我
上面这一段是我在写或者学东西时候的一个思路,我个人依然感觉讲下思路比直接讲技术会好很多,尤其是广大的新同学看到会知道如何从完全没看过学习一个东西,记住你不只有文档,记住源码大于文档(这就是为什么python比c系java系语言好的一个重要因素,你随时可以看各种框架的源码)。
文档中可以看到configure是定义这个model上面有哪些属性的,写过backbone的人都知道,model有两种类型,一个是个体model,一个是结合collection,然而spine并没有,这里我要中间插一句我对框架这种东西的理解。
以前我们对于一个页面,针对每个不同地方的需求写一些js,做一些ajax请求,处理一些dom结束了;现在前端有了更多的追求,单页应用,MVC,各种打包工具;诚然这些东西是为了更好的用户体验,更好的开发效率。
个人感觉到的框架缺点:
学习成本都很重,如果面临项目紧任务重,新人加进来根本无法干活;
而且一旦你用了某一种东西之后,灵活性就非常严重的下降。例如mvc,假设后端有一个接口设计的不规范,前端就会蛋疼的要命,这就是用框架会造成的后果,你必须按他规范来,否则你就要自己做一些事情擦屁股;
单页式MVC非常差的可读性,当ajax操作变多的时候,dom操作你都需要放在js里面,这也是单页式MVC框架里面view的事情,他们会用各种前端的模板语言,然后controll里面会说我用的哪一个模板,然后一个controll可能又有几个controll组成,项目大起来之后,这个地方的可读性非常的差。这时候就会怀念以前一个页面一个html在里面写一写js的时候了,虽然会重复写一些东西(违反DRY原则)。
当然可能是我前端的功力不够,在这瞎扯了,有可能有更好的目录结构或者什么能解决这些问题,然而不能让一个新手或者中手很快上手的东西还是不能吸引我。我还是更喜欢手撸jquery css,最少你让一个学生看1天就能直接来做事情,先不说做的好不好,做一周他应该会有进步,而框架不行,框架最少卡半个月。
有个model,model的概念仅仅是做一个api的封装,也就是简化jquery ajax请求发参数以及接受后实例化的事情
ajax请求能封装一下,jquery的ajax用起来还是比较丑的
用coffee,懒得写js,烦
小,我可以很方便的读源码
这完全可以自己定义一个class来做,摆着不重复造轮子的想法找才找到的spine,这玩意儿很符合我的需求(backbone也很相似)
spine的model封装了ajax, model没有 model和collection的定义,刚好就是符合我的第一点需求(虽然spine他支持model上面的很多其他功能,然而对我就够了)
spine的controller+view共同做一件事情,显示,controller处理一些逻辑,比如click事件,ajax前后的dom变化,这个东西就是把以前写在js的逻辑按模块分了一下,有用。关于controller的大小,我目前更倾向于一个html页面一个app,html中每个不同的模块可能会有不同的controller(或者就1个controll)。
view这个东西我仅仅会用作ajax请求后的以前写在success里面的那一坨html,其他的非html元素直接放在html中,而不放在view里面,这样每个页面的html依然分隔可见。
spine的源码是用coffee写的,非常短,而且每个模块分开,可读性特别好
所以spine仅仅就是干了jquery的活儿,代码看起来更有条理而已。
controller上面一些东西的讲解:
el,是指jquery通过哪个element选中一个dom元素,之后再这个controller里面所有的render,append等等改变dom的方法都会在这个el选中的dom上面操作
className,写的话会给el添加一个class
首先是APP controller, el为body,会给body添加一个class app, Route那里暂时无视,这个demo中没用。
APP级别的el选body的原因是:controller虽然可以在里面初始化其他的controller,但是子controller的el必须这个时候已经在浏览器里面初始化过了,否则jquery自然选中不了,当你有复杂的dom嵌套(尤其是有ajax操作时)注意这里。
比如下面,假设我
FragmentListController, 这个controller做的事情就是ajax取得知识点列表数据,并且已照片墙的形式渲染到
这段demo的结构来自 文档 这里,为毛呢,因为我需要先获得所有的tag,然后根据不同的tag获得对应tag的知识点list,然后append到页面上去,这就导致了会发tag个数的ajax请求,多个请求导致我无法再一个controller里面完成一个render(或者我科学的办法我没想到,总之当时实验了半天)。
所以有两个controller,FragmentItem 是处理每个tag返回的东东,这么说吧,用jquery解释,既然有多次异步调用,那么每次回来的时候都要做一波dom处理,FragmentItem就是干这个事情的。
我们先讲解FragmentListController,constructor里面先
这样当FragmentTag就把他的refresh事件绑定到了FragmentListController的render上面,然而为什么要绑定而不直接调用,render调用
然后这个的view长这个样子,framents.eco, 我想在html的
然后add_fragments根据取回来的FragmentTag 初始化了一堆FragmentItem,FragmentItem的构造跟FragmentListController很相似,不同的是我是直接用jquery调用的render,为毛呢,因为不同的tag的url不同,然而我又没找到如何调用fetch,所以就这么写了,但是感觉无所谓,不要被框架框死。注意我调用render的时候调了下Fragment.fromJSON,这样render的时候items就是一堆Fragment对象,跟fetch没什么区别这样。
fragment-item.eco, 这个没什么说的
所以一组稍微有点复杂的ajax请求,渲染完成了(先调用tag,然后根据tag id的列表调用对应的fragment)。这样页面就有了基本的html数据,剩下的是样式,这个我就不想说什么了。
按照这种思路,demo中的js选好了。
照片墙freewall
图片懒加载
拉钩的hover
视频的东西还没做,看心情再说。
学习css layout一个不错的网站
show time
过年回来终于把第一个demo写完了,根据学堂在线的api,写了一个瀑布流的知识点照片墙一样的东西,鼠标hover的时候在图片切换时有某种direction效果,现在还有些东西需要完善。菊花转起来
打开时瀑布流默认在右边有个很大的留白(估计是 freewall 我没用对)
懒加载加了但是貌似没效果
demo 美国vps 图慢 可能被墙 能不能看随缘
源码 tag为2016-02-16-demo。预警: bower npm的东西我都放到git中了(被坑过),比较大,
开始讲解
作为一个后端学习前端,我决的主要应该讲解学习的方法,而不是具体的技巧思考做点什么
扒了下学堂在线首页,发现是几种列表的集合,几种不同类型课程列表,知识点列表,帖子列表。然而课程点进去是具体的课程,要播放视频还要加课,略微烦躁;帖子没什么意思,看不出什么酷炫的效果;知识点点进去之后发现有个汇总页,而且每分页,可见数据量可以,而且都有图片什么的,点一个进去发现不用注册就可以看视频。嘿嘿嘿,那么就可以做一些事情了。做什么呢,我扒了扒之前收藏夹里面的东西 codrops 上刚好看到两个有点意思的东西
全屏video播放
一个有意思的鼠标经过hover
第二个东西启发我想到了拉钩 上面如下图位置的鼠标hover效果,然后我就想这么多有图的知识点为毛不做个照片墙,鼠标移动过去标题出现就用拉钩的这个效果,点进去开始播视频,好像有点意思。
基础数据准备
找api,没找到一次获取全部知识点的api,但是找到另外一组获得知识点tag: http://www.xuetangx.com/api/v2/fragment/tags
根据tag id获得对应知识点列表: http://www.xuetangx.com/api/v2/fragment/tag/1/
gulp 和 spine 我分别在上面两个帖子中讲过 先搞定gulp 喜闻乐见的跨域问题,有些东西就不说了。 尤其是model获得数据这里我就不再赘述。
先看下最后的目录结构, course有关的东西都没用,之前留下的先留着。
根据上个帖子讲的,准备tag和知识点的两个model
class FragmentTag extends Spine.Model @configure "Tag", "id", "key" @extend Spine.Model.Ajax @url: "/api/v2/fragment/tags" @beforeFromJSON: (data) -> data['tags'] module.exports = FragmentTag class Fragment extends Spine.Model @configure "Fragment", "tag_id" @extend Spine.Model.Ajax url: () => "#{Spine.Model.host}/api/v2/fragment/tag/#{@tag_id}/" @beforeFromJSON: (data) -> data['fragments'] module.exports = Fragment
上一个帖子没有提到的东西
beforeFromJSON,因为这两个api直接返回的是这样下面第一端代码这种结构, 而不是第二段代码的结构,我想要拿到每一个model实体是需要剥离外面那一层的。
{ tags: [ {}, {} ] } { "fragments": [ {}, {} ] }
而不是
[ {}, {} ]
那么怎么做呢,两种选择,看文档或者看源码,个人感觉spine的文档很烂,然而他源码很短,所以当时我直接看的源码而没看文档。
思考流程,这是个ajax操作,肯定在ajax.coffee里面,然后要拿数据肯定已经success了,搜success,两处搜索结果,都在不同的两个recordResponse中,其中这行代码告诉我
@record.trigger('ajaxSuccess', @record, @model.fromJSON(data), status, xhr, settings),trigger的ajaxSuccess看起来就是正常jquery ajax成功的回调(传的参数就像),data他传的是
@model.fromJSON(data),所以一定是在model的这个方法里面,接着我退出vim grep了下fromJSON, 发现在spine.coffee里面,显然是beforeFromJSON这个方法做的我想要的事情。
@beforeFromJSON: (objects) -> objects @fromJSON: (objects) -> return unless objects if typeof objects is 'string' objects = JSON.parse(objects) objects = @beforeFromJSON(objects) if Array.isArray(objects) for value in objects if value instanceof this value else new @(value) else return objects if objects instanceof this new @(objects)
上面这一段是我在写或者学东西时候的一个思路,我个人依然感觉讲下思路比直接讲技术会好很多,尤其是广大的新同学看到会知道如何从完全没看过学习一个东西,记住你不只有文档,记住源码大于文档(这就是为什么python比c系java系语言好的一个重要因素,你随时可以看各种框架的源码)。
文档中可以看到configure是定义这个model上面有哪些属性的,写过backbone的人都知道,model有两种类型,一个是个体model,一个是结合collection,然而spine并没有,这里我要中间插一句我对框架这种东西的理解。
框架是什么
前端的MVC框架出了很多很多,我这里就不说了,我个人的感觉是,除非你写某些大型项目,否则得不偿失。首先框架非常重,框架中号称更好的代码可读性,维护性等等,都拼不过这个框架的学习成本。以前我们对于一个页面,针对每个不同地方的需求写一些js,做一些ajax请求,处理一些dom结束了;现在前端有了更多的追求,单页应用,MVC,各种打包工具;诚然这些东西是为了更好的用户体验,更好的开发效率。
个人感觉到的框架缺点:
学习成本都很重,如果面临项目紧任务重,新人加进来根本无法干活;
而且一旦你用了某一种东西之后,灵活性就非常严重的下降。例如mvc,假设后端有一个接口设计的不规范,前端就会蛋疼的要命,这就是用框架会造成的后果,你必须按他规范来,否则你就要自己做一些事情擦屁股;
单页式MVC非常差的可读性,当ajax操作变多的时候,dom操作你都需要放在js里面,这也是单页式MVC框架里面view的事情,他们会用各种前端的模板语言,然后controll里面会说我用的哪一个模板,然后一个controll可能又有几个controll组成,项目大起来之后,这个地方的可读性非常的差。这时候就会怀念以前一个页面一个html在里面写一写js的时候了,虽然会重复写一些东西(违反DRY原则)。
当然可能是我前端的功力不够,在这瞎扯了,有可能有更好的目录结构或者什么能解决这些问题,然而不能让一个新手或者中手很快上手的东西还是不能吸引我。我还是更喜欢手撸jquery css,最少你让一个学生看1天就能直接来做事情,先不说做的好不好,做一周他应该会有进步,而框架不行,框架最少卡半个月。
我想用的框架
很简单,让我更好的用jquery有个model,model的概念仅仅是做一个api的封装,也就是简化jquery ajax请求发参数以及接受后实例化的事情
ajax请求能封装一下,jquery的ajax用起来还是比较丑的
用coffee,懒得写js,烦
小,我可以很方便的读源码
这完全可以自己定义一个class来做,摆着不重复造轮子的想法找才找到的spine,这玩意儿很符合我的需求(backbone也很相似)
spine的model封装了ajax, model没有 model和collection的定义,刚好就是符合我的第一点需求(虽然spine他支持model上面的很多其他功能,然而对我就够了)
spine的controller+view共同做一件事情,显示,controller处理一些逻辑,比如click事件,ajax前后的dom变化,这个东西就是把以前写在js的逻辑按模块分了一下,有用。关于controller的大小,我目前更倾向于一个html页面一个app,html中每个不同的模块可能会有不同的controller(或者就1个controll)。
view这个东西我仅仅会用作ajax请求后的以前写在success里面的那一坨html,其他的非html元素直接放在html中,而不放在view里面,这样每个页面的html依然分隔可见。
spine的源码是用coffee写的,非常短,而且每个模块分开,可读性特别好
继续讲解,html骨架
跟spine无关的我决的就是这个html骨架,因为spine我看来就是让我更有调理的用jquery的一种东西,如下代码,及时我裸写jquery html也是这么定,我会把ajax返回的东西经过一些逻辑处理,修改<div id="fragment-list"></div>这个dom元素。
所以spine仅仅就是干了jquery的活儿,代码看起来更有条理而已。
<!DOCTYPE html> <html lang="zh-CN" > <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>那些年我们看过的知识点</title> <link rel="stylesheet" href="/assets/vendor/marx/css/marx.min.css" type="text/css" /> <link rel="stylesheet" href="/assets/css/main.css" type="text/css" /> <link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico" /> </head> <body> <h1> 那些年我们看过的知识点 </h1> <div id="header-bar"> <a href="http://weibo.com/duoduo369/home" target="_blank"> <img class="icon-weibo" src="/assets/images/weibo.png" /> </a> <a href="https://github.com/duoduo369/leaning-frontend" target="_blank"> <img class="icon-weibo" src="/assets/images/github.png" /> </a> </div> <div id="fragment-list"></div> <!-- JavaScripts --> <script src="/assets/js/vendor.js"></script> <script src="/assets/vendor/spine/lib/spine.js"></script> <script src="/assets/vendor/spine/lib/route.js"></script> <script src="/assets/vendor/spine/lib/ajax.js"></script> <script src="/assets/js/main.js"></script> </body> </html>
几个controller
controller+view是做显示的,记住这一点。controller上面一些东西的讲解:
el,是指jquery通过哪个element选中一个dom元素,之后再这个controller里面所有的render,append等等改变dom的方法都会在这个el选中的dom上面操作
className,写的话会给el添加一个class
首先是APP controller, el为body,会给body添加一个class app, Route那里暂时无视,这个demo中没用。
APP级别的el选body的原因是:controller虽然可以在里面初始化其他的controller,但是子controller的el必须这个时候已经在浏览器里面初始化过了,否则jquery自然选中不了,当你有复杂的dom嵌套(尤其是有ajax操作时)注意这里。
比如下面,假设我
@fragmentListController = new FragmentListController()的el是在
@courseListController = new CourseListController()中的,如果我把这行注释掉
#@courseListController = new CourseListController(),那么页面什么都不会发生,因为FragmentListController选不中他的dom元素(页面没有)。
config = require "./config.coffee" utils = require './utils.coffee' global.lazy = require('lazyloadjs')() utils.set_model_host() #Courses = require './controllers/main' CourseListController = require './controllers/courses' FragmentListController = require './controllers/fragments' class App extends Spine.Controller el: 'body' className: 'app' constructor: -> super #@courseListController = new CourseListController() @fragmentListController = new FragmentListController() Spine.Route.setup() $ -> new App()
FragmentListController, 这个controller做的事情就是ajax取得知识点列表数据,并且已照片墙的形式渲染到
'#fragment-items'这个dom上。
这段demo的结构来自 文档 这里,为毛呢,因为我需要先获得所有的tag,然后根据不同的tag获得对应tag的知识点list,然后append到页面上去,这就导致了会发tag个数的ajax请求,多个请求导致我无法再一个controller里面完成一个render(或者我科学的办法我没想到,总之当时实验了半天)。
config = require '../config' FragmentTag = require '../models/fragment_tags' Fragment = require '../models/fragment' freewall = require 'freewall' sliphover = require 'sliphover' class FragmentItem extends Spine.Controller el: '#fragment-items' constructor: -> super throw "@item required" unless @item @item.bind("refresh change", @render) $.get @item.url(), (items) => @render(Fragment.fromJSON(items)) template: (items) -> require('../views/fragment-item')(items) render: (items) => @item = items if items @append(@template(@item)) if @item @ class FragmentListController extends Spine.Controller el: '#fragment-list' className: 'fragments' constructor: -> super # 注释掉下面这行放到render最后做,因为FragmentItemController # 需要FragmentListController先render一次才行,因为item中的el # 是由ListControlller动态生成的 #FragmentTag.bind("refresh", @add_fragments) FragmentTag.bind("refresh", @render) FragmentTag.fetch() add_fragments: => tags = FragmentTag.all() fragments = (new FragmentItem(item: new Fragment(tag_id: tag.id)) for tag in tags) @add_fragment fragment for fragment in fragments add_fragment: (item) => @append(item.render()) init_freewall: => wall = new freewall.Freewall("#fragment-items") wall.reset selector: '.fragment-box' duration: 100 animate: true reverse: true cellW: 300 cellH: 300 onResize: -> wall.refresh() wall.fitWidth() $(window).trigger("resize") @el.sliphover caption: 'data-caption' withLink: true template: (items) -> require('../views/fragments')(items) render: => items = FragmentTag.all() @html(@template(items)) @add_fragments() @init_freewall() module.exports = FragmentListController
所以有两个controller,FragmentItem 是处理每个tag返回的东东,这么说吧,用jquery解释,既然有多次异步调用,那么每次回来的时候都要做一波dom处理,FragmentItem就是干这个事情的。
我们先讲解FragmentListController,constructor里面先
FragmentTag.bind("refresh", @render)然后fetch的,为什么,文档的例子,并且,看了下源码fetch完事儿后
@model.refresh(record, options)调用model的refresh, 而refresh会trigger两个event:refresh和change
refresh: (atts) -> atts = @constructor.fromJSON(atts) # ID change, need to do some shifting if atts.id and @id isnt atts.id @changeID(atts.id) # go to the source and load attributes @constructor.irecords[@id].load(atts) @trigger('refresh', this) @trigger('change', this, 'refresh') this
这样当FragmentTag就把他的refresh事件绑定到了FragmentListController的render上面,然而为什么要绑定而不直接调用,render调用
FragmentTag.all()时候源码是这样的
@all: -> @cloneArray(@records),而@records又是在ajax回调完成后才有的,也就是说完成前调用all这玩意儿一直是空的,所以要等model完成ajax后trigger的那个event,然后在取all才有值。
render: => items = FragmentTag.all() @html(@template(items)) @add_fragments() @init_freewall()
然后这个的view长这个样子,framents.eco, 我想在html的
<div id="fragment-list"></div>这个dom里面在套一层来包裹
<div id="fragment-items"></div>
然后add_fragments根据取回来的FragmentTag 初始化了一堆FragmentItem,FragmentItem的构造跟FragmentListController很相似,不同的是我是直接用jquery调用的render,为毛呢,因为不同的tag的url不同,然而我又没找到如何调用fetch,所以就这么写了,但是感觉无所谓,不要被框架框死。注意我调用render的时候调了下Fragment.fromJSON,这样render的时候items就是一堆Fragment对象,跟fetch没什么区别这样。
@item.bind("refresh change", @render) $.get @item.url(), (items) => @render(Fragment.fromJSON(items))
fragment-item.eco, 这个没什么说的
<% for fragment in @: %> <div class='fragment-box'> <a href="<%= fragment.web_address %>" target="_blank"> <img src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="<%= fragment.cover_image %>" title="<%= fragment.title %>" alt="<%= fragment.title %>" data-caption="<%= fragment.title %>" onload="lazy(this)" /> </a> </div> <% end %>
所以一组稍微有点复杂的ajax请求,渲染完成了(先调用tag,然后根据tag id的列表调用对应的fragment)。这样页面就有了基本的html数据,剩下的是样式,这个我就不想说什么了。
关于插件
前端最大的好处是基本你要的效果都有现成的,你把某个效果翻译成英语,然后github一搜,按照星星数排序选前几个看看挑一个就行,然后根据例子撸。按照这种思路,demo中的js选好了。
照片墙freewall
图片懒加载
拉钩的hover
视频的东西还没做,看心情再说。
一些推荐的网址什么的
某个设计有关的网站 然而我并不看学习css layout一个不错的网站
相关文章推荐
- 分享微信开发Html5轻游戏中的几个坑
- GUI - Web前端开发框架
- 如何优雅地处理前端异常?
- WEB前端开发都应知道的jquery小技巧及jquery三个简写
- Zend的MVC机制使用分析(二)
- ASP.NET MVC 4 捆绑和缩小实例介绍
- ASP.NET MVC中将控制器分离到类库的实现
- asp.net实现在非MVC中使用Razor模板引擎的方法
- ASP.NET MVC中的AJAX应用
- 为ASP.NET MVC及WebApi添加路由优先级
- ASP.NET MVC中图表控件的使用方法
- ASP.NET MVC的四种验证编程方式
- 前端开发过程中浏览器版本的两种判定方法
- Bootstrap每天必学之前端开发框架
- 仅30行代码实现Javascript中的MVC
- ASP.NET MVC 3仿Server.Transfer效果的实现方法
- 直接拿来用的15个jQuery代码片段
- 如何在MVC应用程序中使用Jquery
- 10个很棒的jQuery代码片段
- 前端开发部分总结[兼容性、DOM操作、跨域等](持续更新)