├── .gitattributes ├── .gitignore ├── README.md ├── flask_jianshu ├── __init__.py ├── run.py ├── static │ ├── css │ │ └── jquery.fullPage.css │ ├── images │ │ └── jianshubg.png │ └── js │ │ ├── echarts-wordcloud.min.js │ │ ├── echarts.js │ │ ├── jquery-1.8.3.min.js │ │ ├── jquery.fullPage.js │ │ └── jquery.fullPage.min.js ├── templates │ ├── index.html │ └── timeline.html └── user_analysis │ ├── __init__.py │ ├── anlysis_timeline.py │ ├── config.py │ ├── jianshu_timeline.py │ └── note_monitor.py └── scrapy_spider ├── jianshu_spider ├── __init__.py ├── items.py ├── pipelines.py ├── settings.py └── spiders │ ├── __init__.py │ ├── jian_spider.py │ └── timeline_spider.py ├── run_spider.py └── scrapy.cfg /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Cache files 18 | *.iml 19 | *.xml 20 | *.pyc 21 | 22 | 23 | # Windows shortcuts 24 | *.lnk 25 | 26 | # ========================= 27 | # Operating System Files 28 | # ========================= 29 | 30 | # OSX 31 | # ========================= 32 | 33 | .DS_Store 34 | .AppleDouble 35 | .LSOverride 36 | 37 | # Thumbnails 38 | ._* 39 | 40 | # Files that might appear on external disk 41 | .Spotlight-V100 42 | .Trashes 43 | 44 | # Directories potentially created on remote AFP share 45 | .AppleDB 46 | .AppleDesktop 47 | Network Trash Folder 48 | Temporary Items 49 | .apdisk 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jianshu 2 | ### 简书网 http://www.jianshu.com/ 的用户抓取 3 | ### 用户分析:[简书用户数据第一篇](http://www.jianshu.com/p/996ffdb2b05e) 4 | ### 用户个人动态分析:[简书用户数据第一篇](http://www.jianshu.com/p/95cb68fd1cfd) 5 | 6 | # 项目依赖: 7 | 8 | * python3.5以上 9 | * mogodb数据库 10 | 11 | ### scrapy—spider 12 | * scrapy 13 | * fake_useragent 14 | * pymongo 15 | 16 | ### scrapy—spider 17 | * flask 18 | * requests 19 | * lxml 20 | * fake_useragent 21 | * pymongo 22 | * jieba 23 | * pyecharts 24 | 25 | # 其他 26 | 后续补充 27 | -------------------------------------------------------------------------------- /flask_jianshu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malone6/Jianshu/5dba08f03c02643457c5f2428f7c0d8384b53f31/flask_jianshu/__init__.py -------------------------------------------------------------------------------- /flask_jianshu/run.py: -------------------------------------------------------------------------------- 1 | import re 2 | from flask import Flask 3 | from flask_jianshu.user_analysis import anlysis_timeline,config 4 | from flask import request 5 | from flask import render_template, redirect,url_for 6 | from pyecharts import WordCloud 7 | 8 | app = Flask(__name__) 9 | 10 | @app.route('/',methods=['POST','GET']) 11 | def geturl(): 12 | if request.method == 'POST': 13 | form_value = request.form['url'] 14 | match_result = re.match(r'(http://)?(www.jianshu.com/u/)?(\w{6}|\w{12})$',form_value) 15 | if match_result: 16 | user_slug = match_result.groups()[-1] 17 | return redirect(url_for('jianshu_timeline',slug=user_slug)) 18 | else: 19 | return render_template('index.html',error_msg='输入的用户主页有问题!请重新输入!') 20 | return render_template('index.html') 21 | 22 | @app.route('/timeline') 23 | def jianshu_timeline(): 24 | slug = request.args.get('slug') 25 | args = config.TIMELINE_TYPES 26 | user =anlysis_timeline.AnalysisUser(slug,*args) 27 | baseinfo = user.get_baseinfo() 28 | first_tag_time = user.get_first_tag_time() 29 | tags_data = user.tags_data() 30 | all_month_data = user.all_tags_data(time_period='month') 31 | all_day_data = user.all_tags_data(time_period='day') 32 | all_hour_data = user.all_tags_data(time_period='hour') 33 | all_week_data = user.all_tags_data(time_period='week') 34 | share_month_data = user.one_tag_data('share_notes','month') 35 | 36 | week_hour = {} 37 | for each in args[:5]: 38 | if baseinfo[each+'_num']>100: 39 | week_hour[each] = user.tag_week_hour_data(each) 40 | else: 41 | week_hour[each] = [] 42 | # share_week_hour_data = user.tag_week_hour_data('share_notes') 43 | # like_note_week_hour_data = user.tag_week_hour_data('like_notes') 44 | # comment_note_week_hour_data = user.tag_week_hour_data('comment_notes') 45 | # reward_note_week_hour_data = user.tag_week_hour_data('reward_notes') 46 | # like_user_week_hour_data = user.tag_week_hour_data('like_users') 47 | comments = user.get_comment() 48 | return render_template('timeline.html', 49 | baseinfo = baseinfo, 50 | first_tag_time = first_tag_time, 51 | tags_data=tags_data, 52 | month_data = all_month_data, 53 | day_data = all_day_data, 54 | hour_data = all_hour_data, 55 | week_data = all_week_data, 56 | share_month_data=share_month_data[1], 57 | week_hour = week_hour, 58 | comments_num=comments[0], 59 | wordcloud_chart=make_wordcloud(comments[1]), 60 | ) 61 | 62 | 63 | def make_wordcloud(comm_data): 64 | ''' 65 | 由于echarts绘制词云图出现问题,用pyecharts绘制词云图 66 | :param comm_data: 67 | :return: 68 | ''' 69 | name = comm_data.keys() 70 | value = comm_data.values() 71 | wordcloud = WordCloud(width='100%', height=600) 72 | wordcloud.add("", name, value, shape="diamond", word_size_range=[15, 120]) 73 | return wordcloud.render_embed() 74 | 75 | if __name__ == '__main__': 76 | app.run(debug=True) 77 | 78 | # app.run( 79 | # host = '0.0.0.0', 80 | # port = 5000, 81 | # debug = True 82 | # ) -------------------------------------------------------------------------------- /flask_jianshu/static/css/jquery.fullPage.css: -------------------------------------------------------------------------------- 1 | /** 2 | * fullPage 2.4.6 3 | * https://github.com/alvarotrigo/fullPage.js 4 | * MIT licensed 5 | * 6 | * Copyright (C) 2013 alvarotrigo.com - A project by Alvaro Trigo 7 | */ 8 | html, body { 9 | margin: 0; 10 | padding: 0; 11 | overflow:hidden; 12 | 13 | /*Avoid flicker on slides transitions for mobile phones #336 */ 14 | -webkit-tap-highlight-color: rgba(0,0,0,0); 15 | } 16 | #superContainer { 17 | height: 100%; 18 | position: relative; 19 | 20 | /* Touch detection for Windows 8 */ 21 | -ms-touch-action: none; 22 | 23 | /* IE 11 on Windows Phone 8.1*/ 24 | touch-action: none; 25 | } 26 | .fp-section { 27 | position: relative; 28 | -webkit-box-sizing: border-box; /* Safari<=5 Android<=3 */ 29 | -moz-box-sizing: border-box; /* <=28 */ 30 | box-sizing: border-box; 31 | } 32 | .fp-slide { 33 | float: left; 34 | } 35 | .fp-slide, .fp-slidesContainer { 36 | height: 100%; 37 | display: block; 38 | } 39 | .fp-slides { 40 | z-index:1; 41 | height: 100%; 42 | overflow: hidden; 43 | position: relative; 44 | -webkit-transition: all 0.3s ease-out; /* Safari<=6 Android<=4.3 */ 45 | transition: all 0.3s ease-out; 46 | } 47 | .fp-section.fp-table, .fp-slide.fp-table { 48 | display: table; 49 | table-layout:fixed; 50 | width: 100%; 51 | } 52 | .fp-tableCell { 53 | display: table-cell; 54 | vertical-align: middle; 55 | width: 100%; 56 | height: 100%; 57 | } 58 | .fp-slidesContainer { 59 | float: left; 60 | position: relative; 61 | } 62 | .fp-controlArrow { 63 | position: absolute; 64 | z-index: 4; 65 | top: 50%; 66 | cursor: pointer; 67 | width: 0; 68 | height: 0; 69 | border-style: solid; 70 | margin-top: -38px; 71 | } 72 | .fp-controlArrow.fp-prev { 73 | left: 15px; 74 | width: 0; 75 | border-width: 38.5px 34px 38.5px 0; 76 | border-color: transparent #fff transparent transparent; 77 | } 78 | .fp-controlArrow.fp-next { 79 | right: 15px; 80 | border-width: 38.5px 0 38.5px 34px; 81 | border-color: transparent transparent transparent #fff; 82 | } 83 | .fp-scrollable { 84 | overflow: scroll; 85 | 86 | } 87 | .fp-notransition { 88 | -webkit-transition: none !important; 89 | transition: none !important; 90 | } 91 | #fp-nav { 92 | position: fixed; 93 | z-index: 100; 94 | margin-top: -32px; 95 | top: 50%; 96 | opacity: 1; 97 | } 98 | #fp-nav.right { 99 | right: 17px; 100 | } 101 | #fp-nav.left { 102 | left: 17px; 103 | } 104 | .fp-slidesNav{ 105 | position: absolute; 106 | z-index: 4; 107 | left: 50%; 108 | opacity: 1; 109 | } 110 | .fp-slidesNav.bottom { 111 | bottom: 17px; 112 | } 113 | .fp-slidesNav.top { 114 | top: 17px; 115 | } 116 | #fp-nav ul, 117 | .fp-slidesNav ul { 118 | margin: 0; 119 | padding: 0; 120 | } 121 | #fp-nav ul li, 122 | .fp-slidesNav ul li { 123 | display: block; 124 | width: 14px; 125 | height: 13px; 126 | margin: 7px; 127 | position:relative; 128 | } 129 | .fp-slidesNav ul li { 130 | display: inline-block; 131 | } 132 | #fp-nav ul li a, 133 | .fp-slidesNav ul li a { 134 | display: block; 135 | position: relative; 136 | z-index: 1; 137 | width: 100%; 138 | height: 100%; 139 | cursor: pointer; 140 | text-decoration: none; 141 | } 142 | #fp-nav ul li a.active span, 143 | .fp-slidesNav ul li a.active span { 144 | background: #333; 145 | } 146 | #fp-nav ul li a span, 147 | .fp-slidesNav ul li a span { 148 | top: 2px; 149 | left: 2px; 150 | width: 8px; 151 | height: 8px; 152 | border: 1px solid #000; 153 | background: rgba(0, 0, 0, 0); 154 | border-radius: 50%; 155 | position: absolute; 156 | z-index: 1; 157 | } 158 | #fp-nav ul li .fp-tooltip { 159 | position: absolute; 160 | top: -2px; 161 | color: #fff; 162 | font-size: 14px; 163 | font-family: arial, helvetica, sans-serif; 164 | white-space: nowrap; 165 | max-width: 220px; 166 | overflow: hidden; 167 | display: block; 168 | opacity: 0; 169 | width: 0; 170 | } 171 | #fp-nav ul li:hover .fp-tooltip { 172 | -webkit-transition: opacity 0.2s ease-in; 173 | transition: opacity 0.2s ease-in; 174 | width: auto; 175 | opacity: 1; 176 | } 177 | #fp-nav ul li .fp-tooltip.right { 178 | right: 20px; 179 | } 180 | #fp-nav ul li .fp-tooltip.left { 181 | left: 20px; 182 | } 183 | -------------------------------------------------------------------------------- /flask_jianshu/static/images/jianshubg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malone6/Jianshu/5dba08f03c02643457c5f2428f7c0d8384b53f31/flask_jianshu/static/images/jianshubg.png -------------------------------------------------------------------------------- /flask_jianshu/static/js/echarts-wordcloud.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("echarts")):"function"==typeof define&&define.amd?define(["echarts"],e):"object"==typeof exports?exports["echarts-wordcloud"]=e(require("echarts")):t["echarts-wordcloud"]=e(t.echarts)}(this,function(t){return function(t){function e(n){if(r[n])return r[n].exports;var a=r[n]={exports:{},id:n,loaded:!1};return t[n].call(a.exports,a,a.exports,e),a.loaded=!0,a.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){t.exports=r(14)},function(t,e,r){function n(t){if("object"==typeof t&&null!==t){var e=t;if(t instanceof Array){e=[];for(var r=0,a=t.length;a>r;r++)e[r]=n(t[r])}else if(!I(t)&&!T(t)){e={};for(var i in t)t.hasOwnProperty(i)&&(e[i]=n(t[i]))}return e}return t}function a(t,e,r){if(!C(e)||!C(t))return r?n(e):t;for(var i in e)if(e.hasOwnProperty(i)){var o=t[i],u=e[i];!C(u)||!C(o)||M(u)||M(o)||T(u)||T(o)||I(u)||I(o)?!r&&i in t||(t[i]=n(e[i],!0)):a(o,u,r)}return t}function i(t,e){for(var r=t[0],n=1,i=t.length;i>n;n++)r=a(r,t[n],e);return r}function o(t,e){for(var r in e)e.hasOwnProperty(r)&&(t[r]=e[r]);return t}function u(t,e,r){for(var n in e)e.hasOwnProperty(n)&&(r?null!=e[n]:null==t[n])&&(t[n]=e[n]);return t}function c(){return document.createElement("canvas")}function l(){return A||(A=W.createCanvas().getContext("2d")),A}function s(t,e){if(t){if(t.indexOf)return t.indexOf(e);for(var r=0,n=t.length;n>r;r++)if(t[r]===e)return r}return-1}function f(t,e){function r(){}var n=t.prototype;r.prototype=e.prototype,t.prototype=new r;for(var a in n)t.prototype[a]=n[a];t.prototype.constructor=t,t.superClass=e}function h(t,e,r){t="prototype"in t?t.prototype:t,e="prototype"in e?e.prototype:e,u(t,e,r)}function d(t){return t?"string"==typeof t?!1:"number"==typeof t.length:void 0}function g(t,e,r){if(t&&e)if(t.forEach&&t.forEach===z)t.forEach(e,r);else if(t.length===+t.length)for(var n=0,a=t.length;a>n;n++)e.call(r,t[n],n,t);else for(var i in t)t.hasOwnProperty(i)&&e.call(r,t[i],i,t)}function m(t,e,r){if(t&&e){if(t.map&&t.map===q)return t.map(e,r);for(var n=[],a=0,i=t.length;i>a;a++)n.push(e.call(r,t[a],a,t));return n}}function p(t,e,r,n){if(t&&e){if(t.reduce&&t.reduce===j)return t.reduce(e,r,n);for(var a=0,i=t.length;i>a;a++)r=e.call(n,r,t[a],a,t);return r}}function v(t,e,r){if(t&&e){if(t.filter&&t.filter===L)return t.filter(e,r);for(var n=[],a=0,i=t.length;i>a;a++)e.call(r,t[a],a,t)&&n.push(t[a]);return n}}function y(t,e,r){if(t&&e)for(var n=0,a=t.length;a>n;n++)if(e.call(r,t[n],n,t))return t[n]}function w(t,e){var r=D.call(arguments,2);return function(){return t.apply(e,r.concat(D.call(arguments)))}}function x(t){var e=D.call(arguments,1);return function(){return t.apply(this,e.concat(D.call(arguments)))}}function M(t){return"[object Array]"===R.call(t)}function b(t){return"function"==typeof t}function S(t){return"[object String]"===R.call(t)}function C(t){var e=typeof t;return"function"===e||!!t&&"object"==e}function I(t){return!!P[R.call(t)]||t instanceof F}function T(t){return t&&1===t.nodeType&&"string"==typeof t.nodeName}function N(t){for(var e=0,r=arguments.length;r>e;e++)if(null!=arguments[e])return arguments[e]}function E(){return Function.call.apply(D,arguments)}function k(t,e){if(!t)throw new Error(e)}var A,F=r(10),P={"[object Function]":1,"[object RegExp]":1,"[object Date]":1,"[object Error]":1,"[object CanvasGradient]":1},R=Object.prototype.toString,O=Array.prototype,z=O.forEach,L=O.filter,D=O.slice,q=O.map,j=O.reduce,W={inherits:f,mixin:h,clone:n,merge:a,mergeAll:i,extend:o,defaults:u,getContext:l,createCanvas:c,indexOf:s,slice:E,find:y,isArrayLike:d,each:g,map:m,reduce:p,filter:v,bind:w,curry:x,isArray:M,isString:S,isObject:C,isFunction:b,isBuildInObject:I,isDom:T,retrieve:N,assert:k,noop:function(){}};t.exports=W},function(e,r){e.exports=t},function(t,e){function r(t){return t.replace(/^\s+/,"").replace(/\s+$/,"")}var n={},a=1e-4;n.linearMap=function(t,e,r,n){var a=e[1]-e[0],i=r[1]-r[0];if(0===a)return 0===i?r[0]:(r[0]+r[1])/2;if(n)if(a>0){if(t<=e[0])return r[0];if(t>=e[1])return r[1]}else{if(t>=e[0])return r[0];if(t<=e[1])return r[1]}else{if(t===e[0])return r[0];if(t===e[1])return r[1]}return(t-e[0])/a*i+r[0]},n.parsePercent=function(t,e){switch(t){case"center":case"middle":t="50%";break;case"left":case"top":t="0%";break;case"right":case"bottom":t="100%"}return"string"==typeof t?r(t).match(/%$/)?parseFloat(t)/100*e:parseFloat(t):null==t?NaN:+t},n.round=function(t){return+(+t).toFixed(10)},n.asc=function(t){return t.sort(function(t,e){return t-e}),t},n.getPrecision=function(t){if(isNaN(t))return 0;for(var e=1,r=0;Math.round(t*e)/e!==t;)e*=10,r++;return r},n.getPixelPrecision=function(t,e){var r=Math.log,n=Math.LN10,a=Math.floor(r(t[1]-t[0])/n),i=Math.round(r(Math.abs(e[1]-e[0]))/n);return Math.max(-a+i,0)},n.MAX_SAFE_INTEGER=9007199254740991,n.remRadian=function(t){var e=2*Math.PI;return(t%e+e)%e},n.isRadianAroundZero=function(t){return t>-a&&a>t},n.parseDate=function(t){return t instanceof Date?t:new Date("string"==typeof t?t.replace(/-/g,"/"):Math.round(t))},n.quantity=function(t){return Math.pow(10,Math.floor(Math.log(t)/Math.LN10))},n.nice=function(t,e){var r,a=n.quantity(t),i=t/a;return r=e?1.5>i?1:2.5>i?2:4>i?3:7>i?5:10:1>i?1:2>i?2:3>i?3:5>i?5:10,r*a},t.exports=n},function(t,e,r){function n(t,e,r,n){if(!e)return t;var u=i(e[0]),c=o.isArray(u)&&u.length||1;r=r||[],n=n||"extra";for(var l=0;c>l;l++)if(!t[l]){var s=r[l]||n+(l-r.length);t[l]=a(e,l)?{type:"ordinal",name:s}:s}return t}function a(t,e){for(var r=0,n=t.length;n>r;r++){var a=i(t[r]);if(!o.isArray(a))return!1;var a=a[e];if(null!=a&&isFinite(a))return!1;if(o.isString(a)&&"-"!==a)return!0}return!1}function i(t){return o.isArray(t)?t:o.isObject(t)?t.value:t}var o=r(1);t.exports=n},function(t,e,r){function n(t){return isNaN(t)?"-":(t=(t+"").split("."),t[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(t.length>1?"."+t[1]:""))}function a(t){return t.toLowerCase().replace(/-(.)/g,function(t,e){return e.toUpperCase()})}function i(t){var e=t.length;return"number"==typeof t?[t,t,t,t]:2===e?[t[0],t[1],t[0],t[1]]:3===e?[t[0],t[1],t[2],t[1]]:t}function o(t){return String(t).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function u(t,e){return"{"+t+(null==e?"":e)+"}"}function c(t,e){f.isArray(e)||(e=[e]);var r=e.length;if(!r)return"";for(var n=e[0].$vars,a=0;ao;o++)for(var c=0;ct?"0"+t:t}var f=r(1),h=r(3),d=["a","b","c","d","e","f","g"];t.exports={normalizeCssArray:i,addCommas:n,toCamelCase:a,encodeHTML:o,formatTpl:c,formatTime:l}},function(t,e,r){"use strict";function n(t,e,r,n,a){var i=0,o=0;null==n&&(n=1/0),null==a&&(a=1/0);var u=0;e.eachChild(function(c,l){var s,f,h=c.position,d=c.getBoundingRect(),g=e.childAt(l+1),m=g&&g.getBoundingRect();if("horizontal"===t){var p=d.width+(m?-m.x+d.x:0);s=i+p,s>n||c.newline?(i=0,s=p,o+=u+r,u=d.height):u=Math.max(u,d.height)}else{var v=d.height+(m?-m.y+d.y:0);f=o+v,f>a||c.newline?(i+=u+r,o=0,f=v,u=d.width):u=Math.max(u,d.width)}c.newline||(h[0]=i,h[1]=o,"horizontal"===t?i=s+r:o=f+r)})}var a=r(1),i=r(7),o=r(3),u=r(5),c=o.parsePercent,l=a.each,s={},f=["left","right","top","bottom","width","height"];s.box=n,s.vbox=a.curry(n,"vertical"),s.hbox=a.curry(n,"horizontal"),s.getAvailableSize=function(t,e,r){var n=e.width,a=e.height,i=c(t.x,n),o=c(t.y,a),l=c(t.x2,n),s=c(t.y2,a);return(isNaN(i)||isNaN(parseFloat(t.x)))&&(i=0),(isNaN(l)||isNaN(parseFloat(t.x2)))&&(l=n),(isNaN(o)||isNaN(parseFloat(t.y)))&&(o=0),(isNaN(s)||isNaN(parseFloat(t.y2)))&&(s=a),r=u.normalizeCssArray(r||0),{width:Math.max(l-i-r[1]-r[3],0),height:Math.max(s-o-r[0]-r[2],0)}},s.getLayoutRect=function(t,e,r){r=u.normalizeCssArray(r||0);var n=e.width,a=e.height,o=c(t.left,n),l=c(t.top,a),s=c(t.right,n),f=c(t.bottom,a),h=c(t.width,n),d=c(t.height,a),g=r[2]+r[0],m=r[1]+r[3],p=t.aspect;switch(isNaN(h)&&(h=n-s-m-o),isNaN(d)&&(d=a-f-g-l),isNaN(h)&&isNaN(d)&&(p>n/a?h=.8*n:d=.8*a),null!=p&&(isNaN(h)&&(h=p*d),isNaN(d)&&(d=h/p)),isNaN(o)&&(o=n-s-h-m),isNaN(l)&&(l=a-f-d-g),t.left||t.right){case"center":o=n/2-h/2-r[3];break;case"right":o=n-h-m}switch(t.top||t.bottom){case"middle":case"center":l=a/2-d/2-r[0];break;case"bottom":l=a-d-g}o=o||0,l=l||0,isNaN(h)&&(h=n-o-(s||0)),isNaN(d)&&(d=a-l-(f||0));var v=new i(o+r[3],l+r[0],h,d);return v.margin=r,v},s.positionGroup=function(t,e,r,n){var i=t.getBoundingRect();e=a.extend(a.clone(e),{width:i.width,height:i.height}),e=s.getLayoutRect(e,r,n),t.position=[e.x-i.x,e.y-i.y]},s.mergeLayoutParam=function(t,e,r){function n(n){var a={},u=0,c={},s=0,f=r.ignoreSize?1:2;if(l(n,function(e){c[e]=t[e]}),l(n,function(t){i(e,t)&&(a[t]=c[t]=e[t]),o(a,t)&&u++,o(c,t)&&s++}),s!==f&&u){if(u>=f)return a;for(var h=0;hn||r>u||c>i||a>l)},contain:function(t,e){var r=this;return t>=r.x&&t<=r.x+r.width&&e>=r.y&&e<=r.y+r.height},clone:function(){return new n(this.x,this.y,this.width,this.height)},copy:function(t){this.x=t.x,this.y=t.y,this.width=t.width,this.height=t.height}},t.exports=n},function(t,e){var r="undefined"==typeof Float32Array?Array:Float32Array,n={create:function(){var t=new r(6);return n.identity(t),t},identity:function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t},copy:function(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t},mul:function(t,e,r){var n=e[0]*r[0]+e[2]*r[1],a=e[1]*r[0]+e[3]*r[1],i=e[0]*r[2]+e[2]*r[3],o=e[1]*r[2]+e[3]*r[3],u=e[0]*r[4]+e[2]*r[5]+e[4],c=e[1]*r[4]+e[3]*r[5]+e[5];return t[0]=n,t[1]=a,t[2]=i,t[3]=o,t[4]=u,t[5]=c,t},translate:function(t,e,r){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4]+r[0],t[5]=e[5]+r[1],t},rotate:function(t,e,r){var n=e[0],a=e[2],i=e[4],o=e[1],u=e[3],c=e[5],l=Math.sin(r),s=Math.cos(r);return t[0]=n*s+o*l,t[1]=-n*l+o*s,t[2]=a*s+u*l,t[3]=-a*l+s*u,t[4]=s*i+l*c,t[5]=s*c-l*i,t},scale:function(t,e,r){var n=r[0],a=r[1];return t[0]=e[0]*n,t[1]=e[1]*a,t[2]=e[2]*n,t[3]=e[3]*a,t[4]=e[4]*n,t[5]=e[5]*a,t},invert:function(t,e){var r=e[0],n=e[2],a=e[4],i=e[1],o=e[3],u=e[5],c=r*o-i*n;return c?(c=1/c,t[0]=o*c,t[1]=-i*c,t[2]=-n*c,t[3]=r*c,t[4]=(n*u-o*a)*c,t[5]=(i*a-r*u)*c,t):null}};t.exports=n},function(t,e){var r="undefined"==typeof Float32Array?Array:Float32Array,n={create:function(t,e){var n=new r(2);return n[0]=t||0,n[1]=e||0,n},copy:function(t,e){return t[0]=e[0],t[1]=e[1],t},clone:function(t){var e=new r(2);return e[0]=t[0],e[1]=t[1],e},set:function(t,e,r){return t[0]=e,t[1]=r,t},add:function(t,e,r){return t[0]=e[0]+r[0],t[1]=e[1]+r[1],t},scaleAndAdd:function(t,e,r,n){return t[0]=e[0]+r[0]*n,t[1]=e[1]+r[1]*n,t},sub:function(t,e,r){return t[0]=e[0]-r[0],t[1]=e[1]-r[1],t},len:function(t){return Math.sqrt(this.lenSquare(t))},lenSquare:function(t){return t[0]*t[0]+t[1]*t[1]},mul:function(t,e,r){return t[0]=e[0]*r[0],t[1]=e[1]*r[1],t},div:function(t,e,r){return t[0]=e[0]/r[0],t[1]=e[1]/r[1],t},dot:function(t,e){return t[0]*e[0]+t[1]*e[1]},scale:function(t,e,r){return t[0]=e[0]*r,t[1]=e[1]*r,t},normalize:function(t,e){var r=n.len(e);return 0===r?(t[0]=0,t[1]=0):(t[0]=e[0]/r,t[1]=e[1]/r),t},distance:function(t,e){return Math.sqrt((t[0]-e[0])*(t[0]-e[0])+(t[1]-e[1])*(t[1]-e[1]))},distanceSquare:function(t,e){return(t[0]-e[0])*(t[0]-e[0])+(t[1]-e[1])*(t[1]-e[1])},negate:function(t,e){return t[0]=-e[0],t[1]=-e[1],t},lerp:function(t,e,r,n){return t[0]=e[0]+n*(r[0]-e[0]),t[1]=e[1]+n*(r[1]-e[1]),t},applyTransform:function(t,e,r){var n=e[0],a=e[1];return t[0]=r[0]*n+r[2]*a+r[4],t[1]=r[1]*n+r[3]*a+r[5],t},min:function(t,e,r){return t[0]=Math.min(e[0],r[0]),t[1]=Math.min(e[1],r[1]),t},max:function(t,e,r){return t[0]=Math.max(e[0],r[0]),t[1]=Math.max(e[1],r[1]),t}};n.length=n.len,n.lengthSquare=n.lenSquare,n.dist=n.distance,n.distSquare=n.distanceSquare,t.exports=n},function(t,e){var r=function(t){this.colorStops=t||[]};r.prototype={constructor:r,addColorStop:function(t,e){this.colorStops.push({offset:t,color:e})}},t.exports=r},function(t,e,r){var n=r(4),a=r(2);a.extendSeriesModel({type:"series.wordCloud",visualColorAccessPath:"textStyle.normal.color",optionUpdated:function(){var t=this.option;t.gridSize=Math.max(Math.floor(t.gridSize),4)},getInitialData:function(t,e){var r=n(["value"],t.data),i=new a.List(r,this);return i.initData(t.data),i},defaultOption:{maskImage:null,shape:"circle",left:"center",top:"center",width:"70%",height:"80%",sizeRange:[12,60],rotationRange:[-90,90],rotationStep:45,gridSize:8,textStyle:{normal:{fontWeight:"normal"}}}})},function(t,e,r){function n(t,e){return t&&t.getShallow(e)}var a=r(2);a.extendChartView({type:"wordCloud",render:function(t,e,r){var i=this.group;i.removeAll();var o=t.getData(),u=t.get("gridSize");t.layoutInstance.ondraw=function(t,e,r,c){var l=o.getItemModel(r),s=l.getModel("textStyle.normal"),f=l.getModel("textStyle.emphasis"),h=function(t,r){var a=t.ecModel,i=a&&a.getModel("textStyle");return["fontStyle","fontWeight","fontSize","fontFamily"].map(function(a,o){return 2!==o?t.getShallow(a)||r.getShallow(a)||n(i,a):(t.getShallow(a,!0)||Math.round(t===s?e:r.getShallow(a,!0)||e))+"px"}).join(" ")},t=new a.graphic.Text({style:{x:c.info.fillTextOffsetX,y:c.info.fillTextOffsetY+.5*e,text:t,textBaseline:"middle",font:h(s,f)},scale:[1/c.info.mu,1/c.info.mu],position:[(c.gx+c.info.gw/2)*u,(c.gy+c.info.gh/2)*u],rotation:c.rot});t.setStyle(s.getItemStyle()),t.setStyle({fill:o.getItemVisual(r,"color")}),i.add(t),o.setItemGraphicEl(r,t),a.graphic.setHoverStyle(t,a.util.extend(f.getItemStyle(),{font:h(f,s)}))}}})},function(t,e,r){var n,a;window.setImmediate||(window.setImmediate=function(){return window.msSetImmediate||window.webkitSetImmediate||window.mozSetImmediate||window.oSetImmediate||function(){if(!window.postMessage||!window.addEventListener)return null;var t=[void 0],e="zero-timeout-message",r=function(r){var n=t.length;return t.push(r),window.postMessage(e+n.toString(36),"*"),n};return window.addEventListener("message",function(r){if("string"==typeof r.data&&r.data.substr(0,e.length)===e){r.stopImmediatePropagation();var n=parseInt(r.data.substr(e.length),36);t[n]&&(t[n](),t[n]=void 0)}},!0),window.clearImmediate=function(e){t[e]&&(t[e]=void 0)},r}()||function(t){window.setTimeout(t,0)}}()),window.clearImmediate||(window.clearImmediate=function(){return window.msClearImmediate||window.webkitClearImmediate||window.mozClearImmediate||window.oClearImmediate||function(t){window.clearTimeout(t)}}()),function(r){var i=function(){var t=document.createElement("canvas");if(!t||!t.getContext)return!1;var e=t.getContext("2d");return e.getImageData&&e.fillText&&Array.prototype.some&&Array.prototype.push?!0:!1}(),o=function(){if(i){for(var t,e,r=document.createElement("canvas").getContext("2d"),n=20;n;){if(r.font=n.toString(10)+"px sans-serif",r.measureText("W").width===t&&r.measureText("m").width===e)return n+1;t=r.measureText("W").width,e=r.measureText("m").width,n--}return 0}}(),u=function(t){for(var e,r,n=t.length;n;e=Math.floor(Math.random()*n),r=t[--n],t[n]=t[e],t[e]=r);return t},c=function(t,e){function r(t,e){return"hsl("+(360*Math.random()).toFixed()+","+(30*Math.random()+70).toFixed()+"%,"+(Math.random()*(e-t)+t).toFixed()+"%)"}if(i){Array.isArray(t)||(t=[t]),t.forEach(function(e,r){if("string"==typeof e){if(t[r]=document.getElementById(e),!t[r])throw"The element id specified is not found."}else if(!e.tagName&&!e.appendChild)throw"You must pass valid HTML elements, or ID of the element."});var n={list:[],fontFamily:'"Trebuchet MS", "Heiti TC", "微軟正黑體", "Arial Unicode MS", "Droid Fallback Sans", sans-serif',fontWeight:"normal",color:"random-dark",minSize:0,weightFactor:1,clearCanvas:!0,backgroundColor:"#fff",gridSize:8,drawOutOfBound:!1,origin:null,drawMask:!1,maskColor:"rgba(255,0,0,0.3)",maskGapWidth:.3,wait:0,abortThreshold:0,abort:function(){},minRotation:-Math.PI/2,maxRotation:Math.PI/2,rotationStep:.1,shuffle:!0,rotateRatio:.1,shape:"circle",ellipticity:.65,classes:null,hover:null,click:null};if(e)for(var a in e)a in n&&(n[a]=e[a]);if("function"!=typeof n.weightFactor){var c=n.weightFactor;n.weightFactor=function(t){return t*c}}if("function"!=typeof n.shape)switch(n.shape){case"circle":default:n.shape="circle";break;case"cardioid":n.shape=function(t){return 1-Math.sin(t)};break;case"diamond":case"square":n.shape=function(t){var e=t%(2*Math.PI/4);return 1/(Math.cos(e)+Math.sin(e))};break;case"triangle-forward":n.shape=function(t){var e=t%(2*Math.PI/3);return 1/(Math.cos(e)+Math.sqrt(3)*Math.sin(e))};break;case"triangle":case"triangle-upright":n.shape=function(t){var e=(t+3*Math.PI/2)%(2*Math.PI/3);return 1/(Math.cos(e)+Math.sqrt(3)*Math.sin(e))};break;case"pentagon":n.shape=function(t){var e=(t+.955)%(2*Math.PI/5);return 1/(Math.cos(e)+.726543*Math.sin(e))};break;case"star":n.shape=function(t){var e=(t+.955)%(2*Math.PI/10);return(t+.955)%(2*Math.PI/5)-2*Math.PI/10>=0?1/(Math.cos(2*Math.PI/10-e)+3.07768*Math.sin(2*Math.PI/10-e)):1/(Math.cos(e)+3.07768*Math.sin(e))}}n.gridSize=Math.max(Math.floor(n.gridSize),4);var l,s,f,h,d,g,m,p=n.gridSize,v=p-n.maskGapWidth,y=Math.abs(n.maxRotation-n.minRotation),w=Math.min(n.maxRotation,n.minRotation),x=n.rotationStep;switch(n.color){case"random-dark":m=function(){return r(10,50)};break;case"random-light":m=function(){return r(50,90)};break;default:"function"==typeof n.color&&(m=n.color)}var M=null;"function"==typeof n.classes&&(M=n.classes);var b,S=!1,C=[],I=function(t){var e,r,n=t.currentTarget,a=n.getBoundingClientRect();t.touches?(e=t.touches[0].clientX,r=t.touches[0].clientY):(e=t.clientX,r=t.clientY);var i=e-a.left,o=r-a.top,u=Math.floor(i*(n.width/a.width||1)/p),c=Math.floor(o*(n.height/a.height||1)/p);return C[u][c]},T=function(t){var e=I(t);if(b!==e)return b=e,e?void n.hover(e.item,e.dimension,t):void n.hover(void 0,void 0,t)},N=function(t){var e=I(t);e&&(n.click(e.item,e.dimension,t),t.preventDefault())},E=[],k=function(t){if(E[t])return E[t];var e=8*t,r=e,a=[];for(0===t&&a.push([h[0],h[1],0]);r--;){var i=1;"circle"!==n.shape&&(i=n.shape(r/e*2*Math.PI)),a.push([h[0]+t*i*Math.cos(-r/e*2*Math.PI),h[1]+t*i*Math.sin(-r/e*2*Math.PI)*n.ellipticity,r/e*2*Math.PI])}return E[t]=a,a},A=function(){return n.abortThreshold>0&&(new Date).getTime()-g>n.abortThreshold},F=function(){return 0===n.rotateRatio?0:Math.random()>n.rotateRatio?0:0===y?w:w+Math.round(Math.random()*y/x)*x},P=function(t,e,r){var a=!1,i=n.weightFactor(e);if(i<=n.minSize)return!1;var u=1;o>i&&(u=function(){for(var t=2;o>t*i;)t+=2;return t}());var c=document.createElement("canvas"),l=c.getContext("2d",{willReadFrequently:!0});l.font=n.fontWeight+" "+(i*u).toString(10)+"px "+n.fontFamily;var s=l.measureText(t).width/u,f=Math.max(i*u,l.measureText("m").width,l.measureText("W").width)/u,h=s+2*f,d=3*f,g=Math.ceil(h/p),m=Math.ceil(d/p);h=g*p,d=m*p;var v=-s/2,y=.4*-f,w=Math.ceil((h*Math.abs(Math.sin(r))+d*Math.abs(Math.cos(r)))/p),x=Math.ceil((h*Math.abs(Math.cos(r))+d*Math.abs(Math.sin(r)))/p),M=x*p,b=w*p;c.setAttribute("width",M),c.setAttribute("height",b),a&&(document.body.appendChild(c),l.save()),l.scale(1/u,1/u),l.translate(M*u/2,b*u/2),l.rotate(-r),l.font=n.fontWeight+" "+(i*u).toString(10)+"px "+n.fontFamily,l.fillStyle="#000",l.textBaseline="middle",l.fillText(t,v*u,(y+.5*i)*u);var S=l.getImageData(0,0,M,b).data;if(A())return!1;a&&(l.strokeRect(v*u,y,s*u,f*u),l.restore());for(var C,I,T,N=[],E=x,k=[w/2,x/2,w/2,x/2];E--;)for(C=w;C--;){T=p;t:{for(;T--;)for(I=p;I--;)if(S[4*((C*p+T)*M+(E*p+I))+3]){N.push([E,C]),Ek[1]&&(k[1]=E),Ck[2]&&(k[2]=C),a&&(l.fillStyle="rgba(255, 0, 0, 0.5)",l.fillRect(E*p,C*p,p-.5,p-.5));break t}a&&(l.fillStyle="rgba(0, 0, 255, 0.5)",l.fillRect(E*p,C*p,p-.5,p-.5))}}return a&&(l.fillStyle="rgba(0, 255, 0, 0.5)",l.fillRect(k[3]*p,k[0]*p,(k[1]-k[3]+1)*p,(k[2]-k[0]+1)*p)),{mu:u,occupied:N,bounds:k,gw:x,gh:w,fillTextOffsetX:v,fillTextOffsetY:y,fillTextWidth:s,fillTextHeight:f,fontSize:i}},R=function(t,e,r,a,i){for(var o=i.length;o--;){var u=t+i[o][0],c=e+i[o][1];if(u>=s||c>=f||0>u||0>c){if(!n.drawOutOfBound)return!1}else if(!l[u][c])return!1}return!0},O=function(e,r,a,i,o,u,c,l,s){var f,h=a.fontSize;f=m?m(i,o,h,u,c):n.color;var d;d=M?M(i,o,h,u,c):n.classes;var g,v=a.bounds;g={x:(e+v[3])*p,y:(r+v[0])*p,w:(v[1]-v[3]+1)*p,h:(v[2]-v[0]+1)*p},t.forEach(function(t){if(t.getContext){var o=t.getContext("2d"),u=a.mu;o.save(),o.scale(1/u,1/u),o.font=n.fontWeight+" "+(h*u).toString(10)+"px "+n.fontFamily,o.fillStyle=f,o.translate((e+a.gw/2)*p*u,(r+a.gh/2)*p*u),0!==l&&o.rotate(-l),o.textBaseline="middle",o.fillText(i,a.fillTextOffsetX*u,(a.fillTextOffsetY+.5*h)*u),o.restore()}else{var c=document.createElement("span"),g="";g="rotate("+-l/Math.PI*180+"deg) ",1!==a.mu&&(g+="translateX(-"+a.fillTextWidth/4+"px) scale("+1/a.mu+")");var m={position:"absolute",display:"block",font:n.fontWeight+" "+h*a.mu+"px "+n.fontFamily,left:(e+a.gw/2)*p+a.fillTextOffsetX+"px",top:(r+a.gh/2)*p+a.fillTextOffsetY+"px",width:a.fillTextWidth+"px",height:a.fillTextHeight+"px",lineHeight:h+"px",whiteSpace:"nowrap",transform:g,webkitTransform:g,msTransform:g,transformOrigin:"50% 40%",webkitTransformOrigin:"50% 40%",msTransformOrigin:"50% 40%"};f&&(m.color=f),c.textContent=i;for(var v in m)c.style[v]=m[v];if(s)for(var y in s)c.setAttribute(y,s[y]);d&&(c.className+=d),t.appendChild(c)}})},z=function(e,r,n,a,i){if(!(e>=s||r>=f||0>e||0>r)){if(l[e][r]=!1,n){var o=t[0].getContext("2d");o.fillRect(e*p,r*p,v,v)}S&&(C[e][r]={item:i,dimension:a})}},L=function(e,r,a,i,o,u){var c,l=o.occupied,h=n.drawMask;h&&(c=t[0].getContext("2d"),c.save(),c.fillStyle=n.maskColor);var d;if(S){var g=o.bounds;d={x:(e+g[3])*p,y:(r+g[0])*p,w:(g[1]-g[3]+1)*p,h:(g[2]-g[0]+1)*p}}for(var m=l.length;m--;){var v=e+l[m][0],y=r+l[m][1];v>=s||y>=f||0>v||0>y||z(v,y,h,d,u)}h&&c.restore()},D=function(t){var e,r,a;Array.isArray(t)?(e=t[0],r=t[1]):(e=t.word,r=t.weight,a=t.attributes);var i=F(),o=P(e,r,i);if(!o)return!1;if(A())return!1;if(!n.drawOutOfBound){var c=o.bounds;if(c[1]-c[3]+1>s||c[2]-c[0]+1>f)return!1}for(var l=d+1,h=function(n){var u=Math.floor(n[0]-o.gw/2),c=Math.floor(n[1]-o.gh/2),s=o.gw,f=o.gh;return R(u,c,s,f,o.occupied)?(O(u,c,o,e,r,d-l,n[2],i,a),L(u,c,s,f,o,t),{gx:u,gy:c,rot:i,info:o}):!1};l--;){var g=k(d-l);n.shuffle&&(g=[].concat(g),u(g));for(var m=0;m=n.list.length)return x(k),q("wordcloudstop",!1),void I("wordcloudstart",E);g=(new Date).getTime();var t=D(n.list[o]),e=!q("wordclouddrawn",!0,{item:n.list[o],drawn:t});return A()||e?(x(k),n.abort(),q("wordcloudabort",!1),q("wordcloudstop",!1),void I("wordcloudstart",E)):(o++,void(k=w(R,n.wait)))},n.wait)}};j()}};c.isSupported=i,c.minFontSize=o,n=[],a=function(){return c}.apply(e,n),!(void 0!==a&&(t.exports=a))}(this)},function(t,e,r){function n(t){for(var e=t.getContext("2d"),r=e.getImageData(0,0,t.width,t.height),n=e.createImageData(r),a=0;ao||i>384?(n.data[a]=0,n.data[a+1]=0,n.data[a+2]=0,n.data[a+3]=0):(n.data[a]=255,n.data[a+1]=255,n.data[a+2]=255,n.data[a+3]=255)}e.putImageData(n,0,0)}var a=r(2),i=r(6);r(11),r(12);var o=r(13);if(!o.isSupported)throw new Error("Sorry your browser not support wordCloud");a.registerLayout(function(t,e){t.eachSeriesByType("wordCloud",function(r){var u=i.getLayoutRect(r.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()}),c=r.getData(),l=document.createElement("canvas");l.width=u.width,l.height=u.height;var s=l.getContext("2d"),f=r.get("maskImage");if(f)try{s.drawImage(f,0,0,l.width,l.height),n(l)}catch(h){console.error("Invalid mask image"),console.error(h.toString())}var d=r.get("sizeRange"),g=r.get("rotationRange"),m=c.getDataExtent("value"),p=Math.PI/180,v=r.get("gridSize");o(l,{list:c.mapArray("value",function(t,e){var r=c.getItemModel(e);return[c.getName(e),r.get("textStyle.normal.textSize",!0)||a.number.linearMap(t,m,d),e]}).sort(function(t,e){return e[1]-t[1]}),fontFamily:r.get("textStyle.normal.fontFamily")||r.get("textStyle.emphasis.fontFamily")||t.get("textStyle.fontFamily"),fontWeight:r.get("textStyle.normal.fontWeight")||r.get("textStyle.emphasis.fontWeight")||t.get("textStyle.fontWeight"),gridSize:v,ellipticity:u.height/u.width,minRotation:g[0]*p,maxRotation:g[1]*p,clearCanvas:!f,rotateRatio:1,rotationStep:r.get("rotationStep")*p,drawOutOfBound:!1,shuffle:!1,shape:r.get("shape")}),l.addEventListener("wordclouddrawn",function(t){var e=t.detail.item;t.detail.drawn&&r.layoutInstance.ondraw&&(t.detail.drawn.gx+=u.x/v,t.detail.drawn.gy+=u.y/v,r.layoutInstance.ondraw(e[0],e[1],e[2],t.detail.drawn))}),r.layoutInstance={ondraw:null}})})}])}); -------------------------------------------------------------------------------- /flask_jianshu/static/js/jquery.fullPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fullPage 2.5.4 3 | * https://github.com/alvarotrigo/fullPage.js 4 | * MIT licensed 5 | * 6 | * Copyright (C) 2013 alvarotrigo.com - A project by Alvaro Trigo 7 | */ 8 | 9 | (function($) { 10 | $.fn.fullpage = function(options) { 11 | // Create some defaults, extending them with any options that were provided 12 | options = $.extend({ 13 | //navigation 14 | 'menu': false, 15 | 'anchors':[], 16 | 'navigation': false, 17 | 'navigationPosition': 'right', 18 | 'navigationColor': '#000', 19 | 'navigationTooltips': [], 20 | 'slidesNavigation': false, 21 | 'slidesNavPosition': 'bottom', 22 | 'scrollBar': false, 23 | 24 | //scrolling 25 | 'css3': true, 26 | 'scrollingSpeed': 700, 27 | 'autoScrolling': true, 28 | 'easing': 'easeInQuart', 29 | 'easingcss3': 'ease', 30 | 'loopBottom': false, 31 | 'loopTop': false, 32 | 'loopHorizontal': true, 33 | 'continuousVertical': false, 34 | 'normalScrollElements': null, 35 | 'scrollOverflow': false, 36 | 'touchSensitivity': 5, 37 | 'normalScrollElementTouchThreshold': 5, 38 | 39 | //Accessibility 40 | 'keyboardScrolling': true, 41 | 'animateAnchor': true, 42 | 'recordHistory': true, 43 | 44 | //design 45 | 'controlArrows': true, 46 | 'controlArrowColor': '#fff', 47 | "verticalCentered": true, 48 | 'resize': true, 49 | 'sectionsColor' : [], 50 | 'paddingTop': 0, 51 | 'paddingBottom': 0, 52 | 'fixedElements': null, 53 | 'responsive': 0, 54 | 55 | //Custom selectors 56 | 'sectionSelector': '.section', 57 | 'slideSelector': '.slide', 58 | 59 | 60 | //events 61 | 'afterLoad': null, 62 | 'onLeave': null, 63 | 'afterRender': null, 64 | 'afterResize': null, 65 | 'afterReBuild': null, 66 | 'afterSlideLoad': null, 67 | 'onSlideLeave': null 68 | }, options); 69 | 70 | displayWarnings(); 71 | 72 | //easeInQuart animation included in the plugin 73 | $.extend($.easing,{ easeInQuart: function (x, t, b, c, d) { return c*(t/=d)*t*t*t + b; }}); 74 | 75 | //Defines the delay to take place before being able to scroll to the next section 76 | //BE CAREFUL! Not recommened to change it under 400 for a good behavior in laptops and 77 | //Apple devices (laptops, mouses...) 78 | var scrollDelay = 600; 79 | 80 | $.fn.fullpage.setAutoScrolling = function(value, type){ 81 | setVariableState('autoScrolling', value, type); 82 | 83 | var element = $('.fp-section.active'); 84 | 85 | if(options.autoScrolling && !options.scrollBar){ 86 | $('html, body').css({ 87 | 'overflow' : 'hidden', 88 | 'height' : '100%' 89 | }); 90 | 91 | $.fn.fullpage.setRecordHistory(options.recordHistory, 'internal'); 92 | 93 | //for IE touch devices 94 | container.css({ 95 | '-ms-touch-action': 'none', 96 | 'touch-action': 'none' 97 | }); 98 | 99 | if(element.length){ 100 | //moving the container up 101 | silentScroll(element.position().top); 102 | } 103 | 104 | }else{ 105 | $('html, body').css({ 106 | 'overflow' : 'visible', 107 | 'height' : 'initial' 108 | }); 109 | 110 | $.fn.fullpage.setRecordHistory(false, 'internal'); 111 | 112 | //for IE touch devices 113 | container.css({ 114 | '-ms-touch-action': '', 115 | 'touch-action': '' 116 | }); 117 | 118 | silentScroll(0); 119 | 120 | //scrolling the page to the section with no animation 121 | $('html, body').scrollTop(element.position().top); 122 | } 123 | 124 | }; 125 | 126 | /** 127 | * Defines wheter to record the history for each hash change in the URL. 128 | */ 129 | $.fn.fullpage.setRecordHistory = function(value, type){ 130 | setVariableState('recordHistory', value, type); 131 | }; 132 | 133 | /** 134 | * Defines the scrolling speed 135 | */ 136 | $.fn.fullpage.setScrollingSpeed = function(value, type){ 137 | setVariableState('scrollingSpeed', value, type); 138 | }; 139 | 140 | /** 141 | * Adds or remove the possiblity of scrolling through sections by using the mouse wheel or the trackpad. 142 | */ 143 | $.fn.fullpage.setMouseWheelScrolling = function (value){ 144 | if(value){ 145 | addMouseWheelHandler(); 146 | }else{ 147 | removeMouseWheelHandler(); 148 | } 149 | }; 150 | 151 | /** 152 | * Adds or remove the possiblity of scrolling through sections by using the mouse wheel/trackpad or touch gestures. 153 | * Optionally a second parameter can be used to specify the direction for which the action will be applied. 154 | * 155 | * @param directions string containing the direction or directions separated by comma. 156 | */ 157 | $.fn.fullpage.setAllowScrolling = function (value, directions){ 158 | if(typeof directions != 'undefined'){ 159 | directions = directions.replace(' ', '').split(','); 160 | $.each(directions, function (index, direction){ 161 | setIsScrollable(value, direction); 162 | }); 163 | } 164 | else if(value){ 165 | $.fn.fullpage.setMouseWheelScrolling(true); 166 | addTouchHandler(); 167 | }else{ 168 | $.fn.fullpage.setMouseWheelScrolling(false); 169 | removeTouchHandler(); 170 | } 171 | }; 172 | 173 | /** 174 | * Adds or remove the possiblity of scrolling through sections by using the keyboard arrow keys 175 | */ 176 | $.fn.fullpage.setKeyboardScrolling = function (value){ 177 | options.keyboardScrolling = value; 178 | }; 179 | 180 | $.fn.fullpage.moveSectionUp = function(){ 181 | var prev = $('.fp-section.active').prev('.fp-section'); 182 | 183 | //looping to the bottom if there's no more sections above 184 | if (!prev.length && (options.loopTop || options.continuousVertical)) { 185 | prev = $('.fp-section').last(); 186 | } 187 | 188 | if (prev.length) { 189 | scrollPage(prev, null, true); 190 | } 191 | }; 192 | 193 | $.fn.fullpage.moveSectionDown = function (){ 194 | var next = $('.fp-section.active').next('.fp-section'); 195 | 196 | //looping to the top if there's no more sections below 197 | if(!next.length && 198 | (options.loopBottom || options.continuousVertical)){ 199 | next = $('.fp-section').first(); 200 | } 201 | 202 | if(next.length){ 203 | scrollPage(next, null, false); 204 | } 205 | }; 206 | 207 | $.fn.fullpage.moveTo = function (section, slide){ 208 | var destiny = ''; 209 | 210 | if(isNaN(section)){ 211 | destiny = $('[data-anchor="'+section+'"]'); 212 | }else{ 213 | destiny = $('.fp-section').eq( (section -1) ); 214 | } 215 | 216 | if (typeof slide !== 'undefined'){ 217 | scrollPageAndSlide(section, slide); 218 | }else if(destiny.length > 0){ 219 | scrollPage(destiny); 220 | } 221 | }; 222 | 223 | $.fn.fullpage.moveSlideRight = function(){ 224 | moveSlide('next'); 225 | }; 226 | 227 | $.fn.fullpage.moveSlideLeft = function(){ 228 | moveSlide('prev'); 229 | }; 230 | 231 | /** 232 | * When resizing is finished, we adjust the slides sizes and positions 233 | */ 234 | $.fn.fullpage.reBuild = function(resizing){ 235 | isResizing = true; 236 | 237 | var windowsWidth = $(window).width(); 238 | windowsHeight = $(window).height(); //updating global var 239 | 240 | //text and images resizing 241 | if (options.resize) { 242 | resizeMe(windowsHeight, windowsWidth); 243 | } 244 | 245 | $('.fp-section').each(function(){ 246 | var scrollHeight = windowsHeight - parseInt($(this).css('padding-bottom')) - parseInt($(this).css('padding-top')); 247 | 248 | //adjusting the height of the table-cell for IE and Firefox 249 | if(options.verticalCentered){ 250 | $(this).find('.fp-tableCell').css('height', getTableHeight($(this)) + 'px'); 251 | } 252 | 253 | $(this).css('height', windowsHeight + 'px'); 254 | 255 | //resizing the scrolling divs 256 | if(options.scrollOverflow){ 257 | var slides = $(this).find('.fp-slide'); 258 | 259 | if(slides.length){ 260 | slides.each(function(){ 261 | createSlimScrolling($(this)); 262 | }); 263 | }else{ 264 | createSlimScrolling($(this)); 265 | } 266 | } 267 | 268 | //adjusting the position fo the FULL WIDTH slides... 269 | var slides = $(this).find('.fp-slides'); 270 | if (slides.length) { 271 | landscapeScroll(slides, slides.find('.fp-slide.active')); 272 | } 273 | }); 274 | 275 | //adjusting the position for the current section 276 | var destinyPos = $('.fp-section.active').position(); 277 | 278 | var activeSection = $('.fp-section.active'); 279 | 280 | //isn't it the first section? 281 | if(activeSection.index('.fp-section')){ 282 | scrollPage(activeSection); 283 | } 284 | 285 | isResizing = false; 286 | $.isFunction( options.afterResize ) && resizing && options.afterResize.call( this ) 287 | $.isFunction( options.afterReBuild ) && !resizing && options.afterReBuild.call( this ); 288 | } 289 | 290 | //flag to avoid very fast sliding for landscape sliders 291 | var slideMoving = false; 292 | 293 | var isTouchDevice = navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry|BB10|Windows Phone|Tizen|Bada)/); 294 | var isTouch = (('ontouchstart' in window) || (navigator.msMaxTouchPoints > 0) || (navigator.maxTouchPoints)); 295 | var container = $(this); 296 | var windowsHeight = $(window).height(); 297 | var isMoving = false; 298 | var isResizing = false; 299 | var lastScrolledDestiny; 300 | var lastScrolledSlide; 301 | var nav; 302 | var wrapperSelector = 'fullpage-wrapper'; 303 | var isScrollAllowed = { 'up':true, 'down':true, 'left':true, 'right':true }; 304 | var originals = jQuery.extend(true, {}, options); //deep copy 305 | 306 | $.fn.fullpage.setAllowScrolling(true); 307 | 308 | //if css3 is not supported, it will use jQuery animations 309 | if(options.css3){ 310 | options.css3 = support3d(); 311 | } 312 | 313 | if($(this).length){ 314 | container.css({ 315 | 'height': '100%', 316 | 'position': 'relative' 317 | }); 318 | 319 | //adding a class to recognize the container internally in the code 320 | container.addClass(wrapperSelector); 321 | } 322 | 323 | //trying to use fullpage without a selector? 324 | else{ 325 | showError('error', "Error! Fullpage.js needs to be initialized with a selector. For example: $('#myContainer').fullpage();"); 326 | } 327 | 328 | //adding internal class names to void problem with common ones 329 | $(options.sectionSelector).each(function(){ 330 | $(this).addClass('fp-section'); 331 | }); 332 | $(options.slideSelector).each(function(){ 333 | $(this).addClass('fp-slide'); 334 | }); 335 | 336 | //creating the navigation dots 337 | if (options.navigation) { 338 | addVerticalNavigation(); 339 | } 340 | 341 | $('.fp-section').each(function(index){ 342 | var that = $(this); 343 | var slides = $(this).find('.fp-slide'); 344 | var numSlides = slides.length; 345 | 346 | //if no active section is defined, the 1st one will be the default one 347 | if(!index && $('.fp-section.active').length === 0) { 348 | $(this).addClass('active'); 349 | } 350 | 351 | $(this).css('height', windowsHeight + 'px'); 352 | 353 | if(options.paddingTop || options.paddingBottom){ 354 | $(this).css('padding', options.paddingTop + ' 0 ' + options.paddingBottom + ' 0'); 355 | } 356 | 357 | if (typeof options.sectionsColor[index] !== 'undefined') { 358 | $(this).css('background-color', options.sectionsColor[index]); 359 | } 360 | 361 | if (typeof options.anchors[index] !== 'undefined') { 362 | $(this).attr('data-anchor', options.anchors[index]); 363 | } 364 | 365 | // if there's any slide 366 | if (numSlides > 1) { 367 | var sliderWidth = numSlides * 100; 368 | var slideWidth = 100 / numSlides; 369 | 370 | slides.wrapAll('
'); 371 | slides.parent().wrap('
'); 372 | 373 | $(this).find('.fp-slidesContainer').css('width', sliderWidth + '%'); 374 | 375 | if(options.controlArrows){ 376 | createSlideArrows($(this)); 377 | } 378 | 379 | if(options.slidesNavigation){ 380 | addSlidesNavigation($(this), numSlides); 381 | } 382 | 383 | slides.each(function(index) { 384 | $(this).css('width', slideWidth + '%'); 385 | 386 | if(options.verticalCentered){ 387 | addTableClass($(this)); 388 | } 389 | }); 390 | 391 | var startingSlide = that.find('.fp-slide.active'); 392 | 393 | //if the slide won#t be an starting point, the default will be the first one 394 | if(startingSlide.length == 0){ 395 | slides.eq(0).addClass('active'); 396 | } 397 | 398 | //is there a starting point for a non-starting section? 399 | else{ 400 | silentLandscapeScroll(startingSlide); 401 | } 402 | 403 | }else{ 404 | if(options.verticalCentered){ 405 | addTableClass($(this)); 406 | } 407 | } 408 | 409 | }).promise().done(function(){ 410 | $.fn.fullpage.setAutoScrolling(options.autoScrolling, 'internal'); 411 | 412 | //the starting point is a slide? 413 | var activeSlide = $('.fp-section.active').find('.fp-slide.active'); 414 | 415 | //the active section isn't the first one? Is not the first slide of the first section? Then we load that section/slide by default. 416 | if( activeSlide.length && ($('.fp-section.active').index('.fp-section') != 0 || ($('.fp-section.active').index('.fp-section') == 0 && activeSlide.index() != 0))){ 417 | silentLandscapeScroll(activeSlide); 418 | } 419 | 420 | //fixed elements need to be moved out of the plugin container due to problems with CSS3. 421 | if(options.fixedElements && options.css3){ 422 | $(options.fixedElements).appendTo('body'); 423 | } 424 | 425 | //vertical centered of the navigation + first bullet active 426 | if(options.navigation){ 427 | nav.css('margin-top', '-' + (nav.height()/2) + 'px'); 428 | nav.find('li').eq($('.fp-section.active').index('.fp-section')).find('a').addClass('active'); 429 | } 430 | 431 | //moving the menu outside the main container if it is inside (avoid problems with fixed positions when using CSS3 tranforms) 432 | if(options.menu && options.css3 && $(options.menu).closest('.fullpage-wrapper').length){ 433 | $(options.menu).appendTo('body'); 434 | } 435 | 436 | if(options.scrollOverflow){ 437 | if(document.readyState === "complete"){ 438 | createSlimScrollingHandler(); 439 | } 440 | //after DOM and images are loaded 441 | $(window).on('load', createSlimScrollingHandler); 442 | }else{ 443 | $.isFunction( options.afterRender ) && options.afterRender.call( this); 444 | } 445 | 446 | responsive(); 447 | 448 | //getting the anchor link in the URL and deleting the `#` 449 | var value = window.location.hash.replace('#', '').split('/'); 450 | var destiny = value[0]; 451 | 452 | if(destiny.length){ 453 | var section = $('[data-anchor="'+destiny+'"]'); 454 | 455 | if(!options.animateAnchor && section.length){ 456 | 457 | if(options.autoScrolling){ 458 | silentScroll(section.position().top); 459 | } 460 | else{ 461 | silentScroll(0); 462 | setBodyClass(destiny); 463 | 464 | //scrolling the page to the section with no animation 465 | $('html, body').scrollTop(section.position().top); 466 | } 467 | 468 | activateMenuAndNav(destiny, null); 469 | 470 | $.isFunction( options.afterLoad ) && options.afterLoad.call( this, destiny, (section.index('.fp-section') + 1)); 471 | 472 | //updating the active class 473 | section.addClass('active').siblings().removeClass('active'); 474 | } 475 | } 476 | 477 | 478 | $(window).on('load', function() { 479 | scrollToAnchor(); 480 | }); 481 | 482 | }); 483 | 484 | 485 | /** 486 | * Creates the control arrows for the given section 487 | */ 488 | function createSlideArrows(section){ 489 | section.find('.fp-slides').after('
'); 490 | 491 | if(options.controlArrowColor!='#fff'){ 492 | section.find('.fp-controlArrow.fp-next').css('border-color', 'transparent transparent transparent '+options.controlArrowColor); 493 | section.find('.fp-controlArrow.fp-prev').css('border-color', 'transparent '+ options.controlArrowColor + ' transparent transparent'); 494 | } 495 | 496 | if(!options.loopHorizontal){ 497 | section.find('.fp-controlArrow.fp-prev').hide(); 498 | } 499 | } 500 | 501 | /** 502 | * Creates a vertical navigation bar. 503 | */ 504 | function addVerticalNavigation(){ 505 | $('body').append('
    '); 506 | nav = $('#fp-nav'); 507 | 508 | nav.css('color', options.navigationColor); 509 | nav.addClass(options.navigationPosition); 510 | 511 | for (var i = 0; i < $('.fp-section').length; i++) { 512 | var link = ''; 513 | if (options.anchors.length) { 514 | link = options.anchors[i]; 515 | } 516 | 517 | var li = '
  • '; 518 | 519 | // Only add tooltip if needed (defined by user) 520 | var tooltip = options.navigationTooltips[i]; 521 | if (tooltip != undefined && tooltip != '') { 522 | li += '
    ' + tooltip + '
    '; 523 | } 524 | 525 | li += '
  • '; 526 | 527 | nav.find('ul').append(li); 528 | } 529 | } 530 | 531 | function createSlimScrollingHandler(){ 532 | $('.fp-section').each(function(){ 533 | var slides = $(this).find('.fp-slide'); 534 | 535 | if(slides.length){ 536 | slides.each(function(){ 537 | createSlimScrolling($(this)); 538 | }); 539 | }else{ 540 | createSlimScrolling($(this)); 541 | } 542 | 543 | }); 544 | $.isFunction( options.afterRender ) && options.afterRender.call( this); 545 | } 546 | 547 | var scrollId; 548 | var scrollId2; 549 | var isScrolling = false; 550 | 551 | //when scrolling... 552 | $(window).on('scroll', scrollHandler); 553 | 554 | function scrollHandler(){ 555 | if(!options.autoScrolling || options.scrollBar){ 556 | var currentScroll = $(window).scrollTop(); 557 | var visibleSectionIndex = 0; 558 | var initial = Math.abs(currentScroll - $('.fp-section').first().offset().top); 559 | 560 | //taking the section which is showing more content in the viewport 561 | $('.fp-section').each(function(index){ 562 | var current = Math.abs(currentScroll - $(this).offset().top); 563 | 564 | if(current < initial){ 565 | visibleSectionIndex = index; 566 | initial = current; 567 | } 568 | }); 569 | 570 | //geting the last one, the current one on the screen 571 | var currentSection = $('.fp-section').eq(visibleSectionIndex); 572 | } 573 | 574 | if(!options.autoScrolling){ 575 | //executing only once the first time we reach the section 576 | if(!currentSection.hasClass('active')){ 577 | isScrolling = true; 578 | 579 | var leavingSection = $('.fp-section.active').index('.fp-section') + 1; 580 | var yMovement = getYmovement(currentSection); 581 | var anchorLink = currentSection.data('anchor'); 582 | var sectionIndex = currentSection.index('.fp-section') + 1; 583 | var activeSlide = currentSection.find('.fp-slide.active'); 584 | 585 | if(activeSlide.length){ 586 | var slideAnchorLink = activeSlide.data('anchor'); 587 | var slideIndex = activeSlide.index(); 588 | } 589 | 590 | currentSection.addClass('active').siblings().removeClass('active'); 591 | 592 | if(!isMoving){ 593 | $.isFunction( options.onLeave ) && options.onLeave.call( this, leavingSection, sectionIndex, yMovement); 594 | 595 | $.isFunction( options.afterLoad ) && options.afterLoad.call( this, anchorLink, sectionIndex); 596 | } 597 | 598 | activateMenuAndNav(anchorLink, 0); 599 | 600 | if(options.anchors.length && !isMoving){ 601 | //needed to enter in hashChange event when using the menu with anchor links 602 | lastScrolledDestiny = anchorLink; 603 | 604 | setState(slideIndex, slideAnchorLink, anchorLink, sectionIndex); 605 | } 606 | 607 | //small timeout in order to avoid entering in hashChange event when scrolling is not finished yet 608 | clearTimeout(scrollId); 609 | scrollId = setTimeout(function(){ 610 | isScrolling = false; 611 | }, 100); 612 | } 613 | } 614 | 615 | if(options.scrollBar){ 616 | //for the auto adjust of the viewport to fit a whole section 617 | clearTimeout(scrollId2); 618 | scrollId2 = setTimeout(function(){ 619 | if(!isMoving){ 620 | scrollPage(currentSection); 621 | } 622 | }, 1000); 623 | } 624 | } 625 | 626 | 627 | /** 628 | * Determines whether the active section or slide is scrollable through and scrolling bar 629 | */ 630 | function isScrollable(activeSection){ 631 | //if there are landscape slides, we check if the scrolling bar is in the current one or not 632 | if(activeSection.find('.fp-slides').length){ 633 | scrollable= activeSection.find('.fp-slide.active').find('.fp-scrollable'); 634 | }else{ 635 | scrollable = activeSection.find('.fp-scrollable'); 636 | } 637 | 638 | return scrollable; 639 | } 640 | 641 | /** 642 | * Determines the way of scrolling up or down: 643 | * by 'automatically' scrolling a section or by using the default and normal scrolling. 644 | */ 645 | function scrolling(type, scrollable){ 646 | if (!isScrollAllowed[type]){ 647 | return; 648 | } 649 | 650 | if(type == 'down'){ 651 | var check = 'bottom'; 652 | var scrollSection = $.fn.fullpage.moveSectionDown; 653 | }else{ 654 | var check = 'top'; 655 | var scrollSection = $.fn.fullpage.moveSectionUp; 656 | } 657 | 658 | if(scrollable.length > 0 ){ 659 | //is the scrollbar at the start/end of the scroll? 660 | if(isScrolled(check, scrollable)){ 661 | scrollSection(); 662 | }else{ 663 | return true; 664 | } 665 | }else{ 666 | // moved up/down 667 | scrollSection(); 668 | } 669 | } 670 | 671 | 672 | var touchStartY = 0; 673 | var touchStartX = 0; 674 | var touchEndY = 0; 675 | var touchEndX = 0; 676 | 677 | /* Detecting touch events 678 | 679 | * As we are changing the top property of the page on scrolling, we can not use the traditional way to detect it. 680 | * This way, the touchstart and the touch moves shows an small difference between them which is the 681 | * used one to determine the direction. 682 | */ 683 | function touchMoveHandler(event){ 684 | var e = event.originalEvent; 685 | 686 | // additional: if one of the normalScrollElements isn't within options.normalScrollElementTouchThreshold hops up the DOM chain 687 | if (!checkParentForNormalScrollElement(event.target)) { 688 | 689 | if(options.autoScrolling && !options.scrollBar){ 690 | //preventing the easing on iOS devices 691 | event.preventDefault(); 692 | } 693 | 694 | var activeSection = $('.fp-section.active'); 695 | var scrollable = isScrollable(activeSection); 696 | 697 | if (!isMoving && !slideMoving) { //if theres any # 698 | var touchEvents = getEventsPage(e); 699 | 700 | touchEndY = touchEvents['y']; 701 | touchEndX = touchEvents['x']; 702 | 703 | //if movement in the X axys is greater than in the Y and the currect section has slides... 704 | if (activeSection.find('.fp-slides').length && Math.abs(touchStartX - touchEndX) > (Math.abs(touchStartY - touchEndY))) { 705 | 706 | //is the movement greater than the minimum resistance to scroll? 707 | if (Math.abs(touchStartX - touchEndX) > ($(window).width() / 100 * options.touchSensitivity)) { 708 | if (touchStartX > touchEndX) { 709 | if(isScrollAllowed.right){ 710 | $.fn.fullpage.moveSlideRight(); //next 711 | } 712 | } else { 713 | if(isScrollAllowed.left){ 714 | $.fn.fullpage.moveSlideLeft(); //prev 715 | } 716 | } 717 | } 718 | } 719 | 720 | //vertical scrolling (only when autoScrolling is enabled) 721 | else if(options.autoScrolling && !options.scrollBar){ 722 | 723 | //is the movement greater than the minimum resistance to scroll? 724 | if (Math.abs(touchStartY - touchEndY) > ($(window).height() / 100 * options.touchSensitivity)) { 725 | if (touchStartY > touchEndY) { 726 | scrolling('down', scrollable); 727 | } else if (touchEndY > touchStartY) { 728 | scrolling('up', scrollable); 729 | } 730 | } 731 | } 732 | } 733 | } 734 | 735 | } 736 | 737 | /** 738 | * recursive function to loop up the parent nodes to check if one of them exists in options.normalScrollElements 739 | * Currently works well for iOS - Android might need some testing 740 | * @param {Element} el target element / jquery selector (in subsequent nodes) 741 | * @param {int} hop current hop compared to options.normalScrollElementTouchThreshold 742 | * @return {boolean} true if there is a match to options.normalScrollElements 743 | */ 744 | function checkParentForNormalScrollElement (el, hop) { 745 | hop = hop || 0; 746 | var parent = $(el).parent(); 747 | 748 | if (hop < options.normalScrollElementTouchThreshold && 749 | parent.is(options.normalScrollElements) ) { 750 | return true; 751 | } else if (hop == options.normalScrollElementTouchThreshold) { 752 | return false; 753 | } else { 754 | return checkParentForNormalScrollElement(parent, ++hop); 755 | } 756 | } 757 | 758 | function touchStartHandler(event){ 759 | var e = event.originalEvent; 760 | 761 | var touchEvents = getEventsPage(e); 762 | touchStartY = touchEvents['y']; 763 | touchStartX = touchEvents['x']; 764 | } 765 | 766 | 767 | /** 768 | * Detecting mousewheel scrolling 769 | * 770 | * http://blogs.sitepointstatic.com/examples/tech/mouse-wheel/index.html 771 | * http://www.sitepoint.com/html5-javascript-mouse-wheel/ 772 | */ 773 | function MouseWheelHandler(e) { 774 | if(options.autoScrolling){ 775 | // cross-browser wheel delta 776 | e = window.event || e; 777 | var delta = Math.max(-1, Math.min(1, 778 | (e.wheelDelta || -e.deltaY || -e.detail))); 779 | 780 | //preventing to scroll the site on mouse wheel when scrollbar is present 781 | if(options.scrollBar){ 782 | e.preventDefault ? e.preventDefault() : e.returnValue = false; 783 | 784 | } 785 | 786 | var activeSection = $('.fp-section.active'); 787 | var scrollable = isScrollable(activeSection); 788 | 789 | if (!isMoving) { //if theres any # 790 | //scrolling down? 791 | if (delta < 0) { 792 | scrolling('down', scrollable); 793 | 794 | //scrolling up? 795 | }else { 796 | scrolling('up', scrollable); 797 | } 798 | } 799 | 800 | return false; 801 | } 802 | } 803 | 804 | function moveSlide(direction){ 805 | var activeSection = $('.fp-section.active'); 806 | var slides = activeSection.find('.fp-slides'); 807 | 808 | // more than one slide needed and nothing should be sliding 809 | if (!slides.length || slideMoving) { 810 | return; 811 | } 812 | 813 | var currentSlide = slides.find('.fp-slide.active'); 814 | var destiny = null; 815 | 816 | if(direction === 'prev'){ 817 | destiny = currentSlide.prev('.fp-slide'); 818 | }else{ 819 | destiny = currentSlide.next('.fp-slide'); 820 | } 821 | 822 | //isn't there a next slide in the secuence? 823 | if(!destiny.length){ 824 | //respect loopHorizontal settin 825 | if (!options.loopHorizontal) return; 826 | 827 | if(direction === 'prev'){ 828 | destiny = currentSlide.siblings(':last'); 829 | }else{ 830 | destiny = currentSlide.siblings(':first'); 831 | } 832 | } 833 | 834 | slideMoving = true; 835 | 836 | landscapeScroll(slides, destiny); 837 | } 838 | 839 | /** 840 | * Maintains the active slides in the viewport 841 | * (Because he `scroll` animation might get lost with some actions, such as when using continuousVertical) 842 | */ 843 | function keepSlidesPosition(){ 844 | $('.fp-slide.active').each(function(){ 845 | silentLandscapeScroll($(this)); 846 | }); 847 | } 848 | 849 | /** 850 | * Scrolls the site to the given element and scrolls to the slide if a callback is given. 851 | */ 852 | function scrollPage(element, callback, isMovementUp){ 853 | var dest = element.position(); 854 | if(typeof dest === "undefined"){ return; } //there's no element to scroll, leaving the function 855 | 856 | //local variables 857 | var v = { 858 | element: element, 859 | callback: callback, 860 | isMovementUp: isMovementUp, 861 | dest: dest, 862 | dtop: dest.top, 863 | yMovement: getYmovement(element), 864 | anchorLink: element.data('anchor'), 865 | sectionIndex: element.index('.fp-section'), 866 | activeSlide: element.find('.fp-slide.active'), 867 | activeSection: $('.fp-section.active'), 868 | leavingSection: $('.fp-section.active').index('.fp-section') + 1, 869 | 870 | //caching the value of isResizing at the momment the function is called 871 | //because it will be checked later inside a setTimeout and the value might change 872 | localIsResizing: isResizing 873 | }; 874 | 875 | //quiting when destination scroll is the same as the current one 876 | if((v.activeSection.is(element) && !isResizing) || (options.scrollBar && $(window).scrollTop() === v.dtop)){ return; } 877 | 878 | if(v.activeSlide.length){ 879 | var slideAnchorLink = v.activeSlide.data('anchor'); 880 | var slideIndex = v.activeSlide.index(); 881 | } 882 | 883 | // If continuousVertical && we need to wrap around 884 | if (options.autoScrolling && options.continuousVertical && typeof (v.isMovementUp) !== "undefined" && 885 | ((!v.isMovementUp && v.yMovement == 'up') || // Intending to scroll down but about to go up or 886 | (v.isMovementUp && v.yMovement == 'down'))) { // intending to scroll up but about to go down 887 | 888 | v = createInfiniteSections(v); 889 | } 890 | 891 | element.addClass('active').siblings().removeClass('active'); 892 | 893 | //preventing from activating the MouseWheelHandler event 894 | //more than once if the page is scrolling 895 | isMoving = true; 896 | 897 | setState(slideIndex, slideAnchorLink, v.anchorLink, v.sectionIndex); 898 | 899 | //callback (onLeave) if the site is not just resizing and readjusting the slides 900 | $.isFunction(options.onLeave) && !v.localIsResizing && options.onLeave.call(this, v.leavingSection, (v.sectionIndex + 1), v.yMovement); 901 | 902 | performMovement(v); 903 | 904 | //flag to avoid callingn `scrollPage()` twice in case of using anchor links 905 | lastScrolledDestiny = v.anchorLink; 906 | 907 | //avoid firing it twice (as it does also on scroll) 908 | if(options.autoScrolling){ 909 | activateMenuAndNav(v.anchorLink, v.sectionIndex) 910 | } 911 | } 912 | 913 | /** 914 | * Performs the movement (by CSS3 or by jQuery) 915 | */ 916 | function performMovement(v){ 917 | // using CSS3 translate functionality 918 | if (options.css3 && options.autoScrolling && !options.scrollBar) { 919 | 920 | var translate3d = 'translate3d(0px, -' + v.dtop + 'px, 0px)'; 921 | transformContainer(translate3d, true); 922 | 923 | setTimeout(function () { 924 | afterSectionLoads(v); 925 | }, options.scrollingSpeed); 926 | } 927 | 928 | // using jQuery animate 929 | else{ 930 | var scrollSettings = getScrollSettings(v); 931 | 932 | $(scrollSettings.element).animate( 933 | scrollSettings.options 934 | , options.scrollingSpeed, options.easing).promise().done(function () { //only one single callback in case of animating `html, body` 935 | afterSectionLoads(v); 936 | }); 937 | } 938 | } 939 | 940 | /** 941 | * Gets the scrolling settings depending on the plugin autoScrolling option 942 | */ 943 | function getScrollSettings(v){ 944 | var scroll = {}; 945 | 946 | if(options.autoScrolling && !options.scrollBar){ 947 | scroll.options = { 'top': -v.dtop}; 948 | scroll.element = '.'+wrapperSelector; 949 | }else{ 950 | scroll.options = { 'scrollTop': v.dtop}; 951 | scroll.element = 'html, body'; 952 | } 953 | 954 | return scroll; 955 | } 956 | 957 | /** 958 | * Adds sections before or after the current one to create the infinite effect. 959 | */ 960 | function createInfiniteSections(v){ 961 | // Scrolling down 962 | if (!v.isMovementUp) { 963 | // Move all previous sections to after the active section 964 | $(".fp-section.active").after(v.activeSection.prevAll(".fp-section").get().reverse()); 965 | } 966 | else { // Scrolling up 967 | // Move all next sections to before the active section 968 | $(".fp-section.active").before(v.activeSection.nextAll(".fp-section")); 969 | } 970 | 971 | // Maintain the displayed position (now that we changed the element order) 972 | silentScroll($('.fp-section.active').position().top); 973 | 974 | // Maintain the active slides visible in the viewport 975 | keepSlidesPosition(); 976 | 977 | // save for later the elements that still need to be reordered 978 | v.wrapAroundElements = v.activeSection; 979 | 980 | // Recalculate animation variables 981 | v.dest = v.element.position(); 982 | v.dtop = v.dest.top; 983 | v.yMovement = getYmovement(v.element); 984 | 985 | return v; 986 | } 987 | 988 | /** 989 | * Fix section order after continuousVertical changes have been animated 990 | */ 991 | function continuousVerticalFixSectionOrder (v) { 992 | // If continuousVertical is in effect (and autoScrolling would also be in effect then), 993 | // finish moving the elements around so the direct navigation will function more simply 994 | if (!v.wrapAroundElements || !v.wrapAroundElements.length) { 995 | return; 996 | } 997 | 998 | if (v.isMovementUp) { 999 | $('.fp-section:first').before(v.wrapAroundElements); 1000 | } 1001 | else { 1002 | $('.fp-section:last').after(v.wrapAroundElements); 1003 | } 1004 | 1005 | silentScroll($('.fp-section.active').position().top); 1006 | 1007 | // Maintain the active slides visible in the viewport 1008 | keepSlidesPosition(); 1009 | }; 1010 | 1011 | 1012 | /** 1013 | * Actions to do once the section is loaded 1014 | */ 1015 | function afterSectionLoads (v){ 1016 | continuousVerticalFixSectionOrder(v); 1017 | //callback (afterLoad) if the site is not just resizing and readjusting the slides 1018 | $.isFunction(options.afterLoad) && !v.localIsResizing && options.afterLoad.call(this, v.anchorLink, (v.sectionIndex + 1)); 1019 | 1020 | setTimeout(function () { 1021 | isMoving = false; 1022 | $.isFunction(v.callback) && v.callback.call(this); 1023 | }, scrollDelay); 1024 | } 1025 | 1026 | 1027 | /** 1028 | * Scrolls to the anchor in the URL when loading the site 1029 | */ 1030 | function scrollToAnchor(){ 1031 | //getting the anchor link in the URL and deleting the `#` 1032 | var value = window.location.hash.replace('#', '').split('/'); 1033 | var section = value[0]; 1034 | var slide = value[1]; 1035 | 1036 | if(section){ //if theres any # 1037 | scrollPageAndSlide(section, slide); 1038 | } 1039 | } 1040 | 1041 | //detecting any change on the URL to scroll to the given anchor link 1042 | //(a way to detect back history button as we play with the hashes on the URL) 1043 | $(window).on('hashchange', hashChangeHandler); 1044 | 1045 | function hashChangeHandler(){ 1046 | if(!isScrolling){ 1047 | var value = window.location.hash.replace('#', '').split('/'); 1048 | var section = value[0]; 1049 | var slide = value[1]; 1050 | 1051 | if(section.length){ 1052 | //when moving to a slide in the first section for the first time (first time to add an anchor to the URL) 1053 | var isFirstSlideMove = (typeof lastScrolledDestiny === 'undefined'); 1054 | var isFirstScrollMove = (typeof lastScrolledDestiny === 'undefined' && typeof slide === 'undefined' && !slideMoving); 1055 | 1056 | /*in order to call scrollpage() only once for each destination at a time 1057 | It is called twice for each scroll otherwise, as in case of using anchorlinks `hashChange` 1058 | event is fired on every scroll too.*/ 1059 | if ((section && section !== lastScrolledDestiny) && !isFirstSlideMove || isFirstScrollMove || (!slideMoving && lastScrolledSlide != slide )) { 1060 | scrollPageAndSlide(section, slide); 1061 | } 1062 | } 1063 | } 1064 | } 1065 | 1066 | 1067 | /** 1068 | * Sliding with arrow keys, both, vertical and horizontal 1069 | */ 1070 | $(document).keydown(function(e) { 1071 | //Moving the main page with the keyboard arrows if keyboard scrolling is enabled 1072 | if (options.keyboardScrolling && options.autoScrolling) { 1073 | 1074 | //preventing the scroll with arrow keys 1075 | if(e.which == 40 || e.which == 38){ 1076 | e.preventDefault(); 1077 | } 1078 | 1079 | if(!isMoving){ 1080 | switch (e.which) { 1081 | //up 1082 | case 38: 1083 | case 33: 1084 | $.fn.fullpage.moveSectionUp(); 1085 | break; 1086 | 1087 | //down 1088 | case 40: 1089 | case 34: 1090 | $.fn.fullpage.moveSectionDown(); 1091 | break; 1092 | 1093 | //Home 1094 | case 36: 1095 | $.fn.fullpage.moveTo(1); 1096 | break; 1097 | 1098 | //End 1099 | case 35: 1100 | $.fn.fullpage.moveTo( $('.fp-section').length ); 1101 | break; 1102 | 1103 | //left 1104 | case 37: 1105 | $.fn.fullpage.moveSlideLeft(); 1106 | break; 1107 | 1108 | //right 1109 | case 39: 1110 | $.fn.fullpage.moveSlideRight(); 1111 | break; 1112 | 1113 | default: 1114 | return; // exit this handler for other keys 1115 | } 1116 | } 1117 | } 1118 | }); 1119 | 1120 | /** 1121 | * Scrolls to the section when clicking the navigation bullet 1122 | */ 1123 | $(document).on('click touchstart', '#fp-nav a', function(e){ 1124 | e.preventDefault(); 1125 | var index = $(this).parent().index(); 1126 | scrollPage($('.fp-section').eq(index)); 1127 | }); 1128 | 1129 | /** 1130 | * Scrolls the slider to the given slide destination for the given section 1131 | */ 1132 | $(document).on('click touchstart', '.fp-slidesNav a', function(e){ 1133 | e.preventDefault(); 1134 | var slides = $(this).closest('.fp-section').find('.fp-slides'); 1135 | var destiny = slides.find('.fp-slide').eq($(this).closest('li').index()); 1136 | 1137 | landscapeScroll(slides, destiny); 1138 | }); 1139 | 1140 | if(options.normalScrollElements){ 1141 | $(document).on('mouseenter', options.normalScrollElements, function () { 1142 | $.fn.fullpage.setMouseWheelScrolling(false); 1143 | }); 1144 | 1145 | $(document).on('mouseleave', options.normalScrollElements, function(){ 1146 | $.fn.fullpage.setMouseWheelScrolling(true); 1147 | }); 1148 | } 1149 | 1150 | /** 1151 | * Scrolling horizontally when clicking on the slider controls. 1152 | */ 1153 | $('.fp-section').on('click touchstart', '.fp-controlArrow', function() { 1154 | if ($(this).hasClass('fp-prev')) { 1155 | $.fn.fullpage.moveSlideLeft(); 1156 | } else { 1157 | $.fn.fullpage.moveSlideRight(); 1158 | } 1159 | }); 1160 | 1161 | /** 1162 | * Scrolls horizontal sliders. 1163 | */ 1164 | function landscapeScroll(slides, destiny){ 1165 | var destinyPos = destiny.position(); 1166 | var slidesContainer = slides.find('.fp-slidesContainer').parent(); 1167 | var slideIndex = destiny.index(); 1168 | var section = slides.closest('.fp-section'); 1169 | var sectionIndex = section.index('.fp-section'); 1170 | var anchorLink = section.data('anchor'); 1171 | var slidesNav = section.find('.fp-slidesNav'); 1172 | var slideAnchor = destiny.data('anchor'); 1173 | 1174 | //caching the value of isResizing at the momment the function is called 1175 | //because it will be checked later inside a setTimeout and the value might change 1176 | var localIsResizing = isResizing; 1177 | 1178 | if(options.onSlideLeave){ 1179 | var prevSlideIndex = section.find('.fp-slide.active').index(); 1180 | var xMovement = getXmovement(prevSlideIndex, slideIndex); 1181 | 1182 | //if the site is not just resizing and readjusting the slides 1183 | if(!localIsResizing && xMovement!=='none'){ 1184 | $.isFunction( options.onSlideLeave ) && options.onSlideLeave.call( this, anchorLink, (sectionIndex + 1), prevSlideIndex, xMovement); 1185 | } 1186 | } 1187 | 1188 | destiny.addClass('active').siblings().removeClass('active'); 1189 | 1190 | 1191 | if(typeof slideAnchor === 'undefined'){ 1192 | slideAnchor = slideIndex; 1193 | } 1194 | 1195 | if(!options.loopHorizontal && options.controlArrows){ 1196 | //hidding it for the fist slide, showing for the rest 1197 | section.find('.fp-controlArrow.fp-prev').toggle(slideIndex!=0); 1198 | 1199 | //hidding it for the last slide, showing for the rest 1200 | section.find('.fp-controlArrow.fp-next').toggle(!destiny.is(':last-child')); 1201 | } 1202 | 1203 | //only changing the URL if the slides are in the current section (not for resize re-adjusting) 1204 | if(section.hasClass('active')){ 1205 | setState(slideIndex, slideAnchor, anchorLink, sectionIndex); 1206 | } 1207 | 1208 | var afterSlideLoads = function(){ 1209 | //if the site is not just resizing and readjusting the slides 1210 | if(!localIsResizing){ 1211 | $.isFunction( options.afterSlideLoad ) && options.afterSlideLoad.call( this, anchorLink, (sectionIndex + 1), slideAnchor, slideIndex); 1212 | } 1213 | //letting them slide again 1214 | slideMoving = false; 1215 | }; 1216 | 1217 | if(options.css3){ 1218 | var translate3d = 'translate3d(-' + destinyPos.left + 'px, 0px, 0px)'; 1219 | 1220 | addAnimation(slides.find('.fp-slidesContainer'), options.scrollingSpeed>0).css(getTransforms(translate3d)); 1221 | 1222 | setTimeout(function(){ 1223 | afterSlideLoads(); 1224 | }, options.scrollingSpeed, options.easing); 1225 | }else{ 1226 | slidesContainer.animate({ 1227 | scrollLeft : destinyPos.left 1228 | }, options.scrollingSpeed, options.easing, function() { 1229 | 1230 | afterSlideLoads(); 1231 | }); 1232 | } 1233 | 1234 | slidesNav.find('.active').removeClass('active'); 1235 | slidesNav.find('li').eq(slideIndex).find('a').addClass('active'); 1236 | } 1237 | 1238 | //when resizing the site, we adjust the heights of the sections, slimScroll... 1239 | $(window).resize(resizeHandler); 1240 | 1241 | var previousHeight = windowsHeight; 1242 | var resizeId; 1243 | function resizeHandler(){ 1244 | //checking if it needs to get responsive 1245 | responsive(); 1246 | 1247 | // rebuild immediately on touch devices 1248 | if (isTouchDevice) { 1249 | 1250 | //if the keyboard is visible 1251 | if ($(document.activeElement).attr('type') !== 'text') { 1252 | var currentHeight = $(window).height(); 1253 | 1254 | //making sure the change in the viewport size is enough to force a rebuild. (20 % of the window to avoid problems when hidding scroll bars) 1255 | if( Math.abs(currentHeight - previousHeight) > (20 * Math.max(previousHeight, currentHeight) / 100) ){ 1256 | $.fn.fullpage.reBuild(true); 1257 | previousHeight = currentHeight; 1258 | } 1259 | } 1260 | }else{ 1261 | //in order to call the functions only when the resize is finished 1262 | //http://stackoverflow.com/questions/4298612/jquery-how-to-call-resize-event-only-once-its-finished-resizing 1263 | clearTimeout(resizeId); 1264 | 1265 | resizeId = setTimeout(function(){ 1266 | $.fn.fullpage.reBuild(true); 1267 | }, 500); 1268 | } 1269 | } 1270 | 1271 | /** 1272 | * Checks if the site needs to get responsive and disables autoScrolling if so. 1273 | * A class `fp-responsive` is added to the plugin's container in case the user wants to use it for his own responsive CSS. 1274 | */ 1275 | function responsive(){ 1276 | if(options.responsive){ 1277 | var isResponsive = container.hasClass('fp-responsive'); 1278 | if ($(window).width() < options.responsive ){ 1279 | if(!isResponsive){ 1280 | $.fn.fullpage.setAutoScrolling(false, 'internal'); 1281 | $('#fp-nav').hide(); 1282 | container.addClass('fp-responsive'); 1283 | } 1284 | }else if(isResponsive){ 1285 | $.fn.fullpage.setAutoScrolling(originals.autoScrolling, 'internal'); 1286 | $('#fp-nav').show(); 1287 | container.removeClass('fp-responsive'); 1288 | } 1289 | } 1290 | } 1291 | 1292 | /** 1293 | * Adds transition animations for the given element 1294 | */ 1295 | function addAnimation(element){ 1296 | var transition = 'all ' + options.scrollingSpeed + 'ms ' + options.easingcss3; 1297 | 1298 | element.removeClass('fp-notransition'); 1299 | return element.css({ 1300 | '-webkit-transition': transition, 1301 | 'transition': transition 1302 | }); 1303 | } 1304 | 1305 | /** 1306 | * Remove transition animations for the given element 1307 | */ 1308 | function removeAnimation(element){ 1309 | return element.addClass('fp-notransition'); 1310 | } 1311 | 1312 | /** 1313 | * Resizing of the font size depending on the window size as well as some of the images on the site. 1314 | */ 1315 | function resizeMe(displayHeight, displayWidth) { 1316 | //Standard dimensions, for which the body font size is correct 1317 | var preferredHeight = 825; 1318 | var preferredWidth = 900; 1319 | 1320 | if (displayHeight < preferredHeight || displayWidth < preferredWidth) { 1321 | var heightPercentage = (displayHeight * 100) / preferredHeight; 1322 | var widthPercentage = (displayWidth * 100) / preferredWidth; 1323 | var percentage = Math.min(heightPercentage, widthPercentage); 1324 | var newFontSize = percentage.toFixed(2); 1325 | 1326 | $("body").css("font-size", newFontSize + '%'); 1327 | } else { 1328 | $("body").css("font-size", '100%'); 1329 | } 1330 | } 1331 | 1332 | /** 1333 | * Activating the website navigation dots according to the given slide name. 1334 | */ 1335 | function activateNavDots(name, sectionIndex){ 1336 | if(options.navigation){ 1337 | $('#fp-nav').find('.active').removeClass('active'); 1338 | if(name){ 1339 | $('#fp-nav').find('a[href="#' + name + '"]').addClass('active'); 1340 | }else{ 1341 | $('#fp-nav').find('li').eq(sectionIndex).find('a').addClass('active'); 1342 | } 1343 | } 1344 | } 1345 | 1346 | /** 1347 | * Activating the website main menu elements according to the given slide name. 1348 | */ 1349 | function activateMenuElement(name){ 1350 | if(options.menu){ 1351 | $(options.menu).find('.active').removeClass('active'); 1352 | $(options.menu).find('[data-menuanchor="'+name+'"]').addClass('active'); 1353 | } 1354 | } 1355 | 1356 | function activateMenuAndNav(anchor, index){ 1357 | activateMenuElement(anchor); 1358 | activateNavDots(anchor, index); 1359 | } 1360 | 1361 | /** 1362 | * Return a boolean depending on whether the scrollable element is at the end or at the start of the scrolling 1363 | * depending on the given type. 1364 | */ 1365 | function isScrolled(type, scrollable){ 1366 | if(type === 'top'){ 1367 | return !scrollable.scrollTop(); 1368 | }else if(type === 'bottom'){ 1369 | return scrollable.scrollTop() + 1 + scrollable.innerHeight() >= scrollable[0].scrollHeight; 1370 | } 1371 | } 1372 | 1373 | /** 1374 | * Retuns `up` or `down` depending on the scrolling movement to reach its destination 1375 | * from the current section. 1376 | */ 1377 | function getYmovement(destiny){ 1378 | var fromIndex = $('.fp-section.active').index('.fp-section'); 1379 | var toIndex = destiny.index('.fp-section'); 1380 | if( fromIndex == toIndex){ 1381 | return 'none' 1382 | } 1383 | if(fromIndex > toIndex){ 1384 | return 'up'; 1385 | } 1386 | return 'down'; 1387 | } 1388 | 1389 | /** 1390 | * Retuns `right` or `left` depending on the scrolling movement to reach its destination 1391 | * from the current slide. 1392 | */ 1393 | function getXmovement(fromIndex, toIndex){ 1394 | if( fromIndex == toIndex){ 1395 | return 'none' 1396 | } 1397 | if(fromIndex > toIndex){ 1398 | return 'left'; 1399 | } 1400 | return 'right'; 1401 | } 1402 | 1403 | 1404 | function createSlimScrolling(element){ 1405 | //needed to make `scrollHeight` work under Opera 12 1406 | element.css('overflow', 'hidden'); 1407 | 1408 | //in case element is a slide 1409 | var section = element.closest('.fp-section'); 1410 | var scrollable = element.find('.fp-scrollable'); 1411 | 1412 | //if there was scroll, the contentHeight will be the one in the scrollable section 1413 | if(scrollable.length){ 1414 | var contentHeight = scrollable.get(0).scrollHeight; 1415 | }else{ 1416 | var contentHeight = element.get(0).scrollHeight; 1417 | if(options.verticalCentered){ 1418 | contentHeight = element.find('.fp-tableCell').get(0).scrollHeight; 1419 | } 1420 | } 1421 | 1422 | var scrollHeight = windowsHeight - parseInt(section.css('padding-bottom')) - parseInt(section.css('padding-top')); 1423 | 1424 | //needs scroll? 1425 | if ( contentHeight > scrollHeight) { 1426 | //was there already an scroll ? Updating it 1427 | if(scrollable.length){ 1428 | scrollable.css('height', scrollHeight + 'px').parent().css('height', scrollHeight + 'px'); 1429 | } 1430 | //creating the scrolling 1431 | else{ 1432 | if(options.verticalCentered){ 1433 | element.find('.fp-tableCell').wrapInner('
    '); 1434 | }else{ 1435 | element.wrapInner('
    '); 1436 | } 1437 | 1438 | element.find('.fp-scrollable').slimScroll({ 1439 | allowPageScroll: true, 1440 | height: scrollHeight + 'px', 1441 | size: '10px', 1442 | alwaysVisible: true 1443 | }); 1444 | } 1445 | } 1446 | 1447 | //removing the scrolling when it is not necessary anymore 1448 | else{ 1449 | removeSlimScroll(element); 1450 | } 1451 | 1452 | //undo 1453 | element.css('overflow', ''); 1454 | } 1455 | 1456 | function removeSlimScroll(element){ 1457 | element.find('.fp-scrollable').children().first().unwrap().unwrap(); 1458 | element.find('.slimScrollBar').remove(); 1459 | element.find('.slimScrollRail').remove(); 1460 | } 1461 | 1462 | function addTableClass(element){ 1463 | element.addClass('fp-table').wrapInner('
    '); 1464 | } 1465 | 1466 | function getTableHeight(element){ 1467 | var sectionHeight = windowsHeight; 1468 | 1469 | if(options.paddingTop || options.paddingBottom){ 1470 | var section = element; 1471 | if(!section.hasClass('fp-section')){ 1472 | section = element.closest('.fp-section'); 1473 | } 1474 | 1475 | var paddings = parseInt(section.css('padding-top')) + parseInt(section.css('padding-bottom')); 1476 | sectionHeight = (windowsHeight - paddings); 1477 | } 1478 | 1479 | return sectionHeight; 1480 | } 1481 | 1482 | /** 1483 | * Adds a css3 transform property to the container class with or without animation depending on the animated param. 1484 | */ 1485 | function transformContainer(translate3d, animated){ 1486 | if(animated){ 1487 | addAnimation(container); 1488 | }else{ 1489 | removeAnimation(container); 1490 | } 1491 | 1492 | container.css(getTransforms(translate3d)); 1493 | 1494 | //syncronously removing the class after the animation has been applied. 1495 | setTimeout(function(){ 1496 | container.removeClass('fp-notransition'); 1497 | },10) 1498 | } 1499 | 1500 | 1501 | /** 1502 | * Scrolls to the given section and slide 1503 | */ 1504 | function scrollPageAndSlide(destiny, slide){ 1505 | if (typeof slide === 'undefined') { 1506 | slide = 0; 1507 | } 1508 | 1509 | if(isNaN(destiny)){ 1510 | var section = $('[data-anchor="'+destiny+'"]'); 1511 | }else{ 1512 | var section = $('.fp-section').eq( (destiny -1) ); 1513 | } 1514 | 1515 | 1516 | //we need to scroll to the section and then to the slide 1517 | if (destiny !== lastScrolledDestiny && !section.hasClass('active')){ 1518 | scrollPage(section, function(){ 1519 | scrollSlider(section, slide) 1520 | }); 1521 | } 1522 | //if we were already in the section 1523 | else{ 1524 | scrollSlider(section, slide); 1525 | } 1526 | } 1527 | 1528 | /** 1529 | * Scrolls the slider to the given slide destination for the given section 1530 | */ 1531 | function scrollSlider(section, slide){ 1532 | if(typeof slide != 'undefined'){ 1533 | var slides = section.find('.fp-slides'); 1534 | var destiny = slides.find('[data-anchor="'+slide+'"]'); 1535 | 1536 | if(!destiny.length){ 1537 | destiny = slides.find('.fp-slide').eq(slide); 1538 | } 1539 | 1540 | if(destiny.length){ 1541 | landscapeScroll(slides, destiny); 1542 | } 1543 | } 1544 | } 1545 | 1546 | /** 1547 | * Creates a landscape navigation bar with dots for horizontal sliders. 1548 | */ 1549 | function addSlidesNavigation(section, numSlides){ 1550 | section.append('
      '); 1551 | var nav = section.find('.fp-slidesNav'); 1552 | 1553 | //top or bottom 1554 | nav.addClass(options.slidesNavPosition); 1555 | 1556 | for(var i=0; i< numSlides; i++){ 1557 | nav.find('ul').append('
    • '); 1558 | } 1559 | 1560 | //centering it 1561 | nav.css('margin-left', '-' + (nav.width()/2) + 'px'); 1562 | 1563 | nav.find('li').first().find('a').addClass('active'); 1564 | } 1565 | 1566 | 1567 | /** 1568 | * Sets the state of the website depending on the active section/slide. 1569 | * It changes the URL hash when needed and updates the body class. 1570 | */ 1571 | function setState(slideIndex, slideAnchor, anchorLink, sectionIndex){ 1572 | var sectionHash = ''; 1573 | 1574 | if(options.anchors.length){ 1575 | 1576 | //isn't it the first slide? 1577 | if(slideIndex){ 1578 | if(typeof anchorLink !== 'undefined'){ 1579 | sectionHash = anchorLink; 1580 | } 1581 | 1582 | //slide without anchor link? We take the index instead. 1583 | if(typeof slideAnchor === 'undefined'){ 1584 | slideAnchor = slideIndex; 1585 | } 1586 | 1587 | lastScrolledSlide = slideAnchor; 1588 | setUrlHash(sectionHash + '/' + slideAnchor); 1589 | 1590 | //first slide won't have slide anchor, just the section one 1591 | }else if(typeof slideIndex !== 'undefined'){ 1592 | lastScrolledSlide = slideAnchor; 1593 | setUrlHash(anchorLink); 1594 | } 1595 | 1596 | //section without slides 1597 | else{ 1598 | setUrlHash(anchorLink); 1599 | } 1600 | 1601 | setBodyClass(location.hash); 1602 | } 1603 | else if(typeof slideIndex !== 'undefined'){ 1604 | setBodyClass(sectionIndex + '-' + slideIndex); 1605 | } 1606 | else{ 1607 | setBodyClass(String(sectionIndex)); 1608 | } 1609 | } 1610 | 1611 | /** 1612 | * Sets the URL hash. 1613 | */ 1614 | function setUrlHash(url){ 1615 | if(options.recordHistory){ 1616 | location.hash = url; 1617 | }else{ 1618 | //Mobile Chrome doesn't work the normal way, so... lets use HTML5 for phones :) 1619 | if(isTouchDevice || isTouch){ 1620 | history.replaceState(undefined, undefined, "#" + url) 1621 | }else{ 1622 | var baseUrl = window.location.href.split('#')[0]; 1623 | window.location.replace( baseUrl + '#' + url ); 1624 | } 1625 | } 1626 | } 1627 | 1628 | /** 1629 | * Sets a class for the body of the page depending on the active section / slide 1630 | */ 1631 | function setBodyClass(text){ 1632 | //changing slash for dash to make it a valid CSS style 1633 | text = text.replace('/', '-').replace('#',''); 1634 | 1635 | //removing previous anchor classes 1636 | $("body")[0].className = $("body")[0].className.replace(/\b\s?fp-viewing-[^\s]+\b/g, ''); 1637 | 1638 | //adding the current anchor 1639 | $("body").addClass("fp-viewing-" + text); 1640 | } 1641 | 1642 | /** 1643 | * Checks for translate3d support 1644 | * @return boolean 1645 | * http://stackoverflow.com/questions/5661671/detecting-transform-translate3d-support 1646 | */ 1647 | function support3d() { 1648 | var el = document.createElement('p'), 1649 | has3d, 1650 | transforms = { 1651 | 'webkitTransform':'-webkit-transform', 1652 | 'OTransform':'-o-transform', 1653 | 'msTransform':'-ms-transform', 1654 | 'MozTransform':'-moz-transform', 1655 | 'transform':'transform' 1656 | }; 1657 | 1658 | // Add it to the body to get the computed style. 1659 | document.body.insertBefore(el, null); 1660 | 1661 | for (var t in transforms) { 1662 | if (el.style[t] !== undefined) { 1663 | el.style[t] = "translate3d(1px,1px,1px)"; 1664 | has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]); 1665 | } 1666 | } 1667 | 1668 | document.body.removeChild(el); 1669 | 1670 | return (has3d !== undefined && has3d.length > 0 && has3d !== "none"); 1671 | } 1672 | 1673 | 1674 | 1675 | /** 1676 | * Removes the auto scrolling action fired by the mouse wheel and trackpad. 1677 | * After this function is called, the mousewheel and trackpad movements won't scroll through sections. 1678 | */ 1679 | function removeMouseWheelHandler(){ 1680 | if (document.addEventListener) { 1681 | document.removeEventListener('mousewheel', MouseWheelHandler, false); //IE9, Chrome, Safari, Oper 1682 | document.removeEventListener('wheel', MouseWheelHandler, false); //Firefox 1683 | } else { 1684 | document.detachEvent("onmousewheel", MouseWheelHandler); //IE 6/7/8 1685 | } 1686 | } 1687 | 1688 | 1689 | /** 1690 | * Adds the auto scrolling action for the mouse wheel and trackpad. 1691 | * After this function is called, the mousewheel and trackpad movements will scroll through sections 1692 | */ 1693 | function addMouseWheelHandler(){ 1694 | if (document.addEventListener) { 1695 | document.addEventListener("mousewheel", MouseWheelHandler, false); //IE9, Chrome, Safari, Oper 1696 | document.addEventListener("wheel", MouseWheelHandler, false); //Firefox 1697 | } else { 1698 | document.attachEvent("onmousewheel", MouseWheelHandler); //IE 6/7/8 1699 | } 1700 | } 1701 | 1702 | 1703 | /** 1704 | * Adds the possibility to auto scroll through sections on touch devices. 1705 | */ 1706 | function addTouchHandler(){ 1707 | if(isTouchDevice || isTouch){ 1708 | //Microsoft pointers 1709 | MSPointer = getMSPointer(); 1710 | 1711 | $(document).off('touchstart ' + MSPointer.down).on('touchstart ' + MSPointer.down, touchStartHandler); 1712 | $(document).off('touchmove ' + MSPointer.move).on('touchmove ' + MSPointer.move, touchMoveHandler); 1713 | } 1714 | } 1715 | 1716 | /** 1717 | * Removes the auto scrolling for touch devices. 1718 | */ 1719 | function removeTouchHandler(){ 1720 | if(isTouchDevice || isTouch){ 1721 | //Microsoft pointers 1722 | MSPointer = getMSPointer(); 1723 | 1724 | $(document).off('touchstart ' + MSPointer.down); 1725 | $(document).off('touchmove ' + MSPointer.move); 1726 | } 1727 | } 1728 | 1729 | 1730 | /* 1731 | * Returns and object with Microsoft pointers (for IE<11 and for IE >= 11) 1732 | * http://msdn.microsoft.com/en-us/library/ie/dn304886(v=vs.85).aspx 1733 | */ 1734 | function getMSPointer(){ 1735 | var pointer; 1736 | 1737 | //IE >= 11 & rest of browsers 1738 | if(window.PointerEvent){ 1739 | pointer = { down: "pointerdown", move: "pointermove"}; 1740 | } 1741 | 1742 | //IE < 11 1743 | else{ 1744 | pointer = { down: "MSPointerDown", move: "MSPointerMove"}; 1745 | } 1746 | 1747 | return pointer; 1748 | } 1749 | /** 1750 | * Gets the pageX and pageY properties depending on the browser. 1751 | * https://github.com/alvarotrigo/fullPage.js/issues/194#issuecomment-34069854 1752 | */ 1753 | function getEventsPage(e){ 1754 | var events = new Array(); 1755 | 1756 | events['y'] = (typeof e.pageY !== 'undefined' && (e.pageY || e.pageX) ? e.pageY : e.touches[0].pageY); 1757 | events['x'] = (typeof e.pageX !== 'undefined' && (e.pageY || e.pageX) ? e.pageX : e.touches[0].pageX); 1758 | 1759 | return events; 1760 | } 1761 | 1762 | function silentLandscapeScroll(activeSlide){ 1763 | $.fn.fullpage.setScrollingSpeed (0, 'internal'); 1764 | landscapeScroll(activeSlide.closest('.fp-slides'), activeSlide); 1765 | $.fn.fullpage.setScrollingSpeed(originals.scrollingSpeed, 'internal'); 1766 | } 1767 | 1768 | function silentScroll(top){ 1769 | if(options.scrollBar){ 1770 | container.scrollTop(top); 1771 | } 1772 | else if (options.css3) { 1773 | var translate3d = 'translate3d(0px, -' + top + 'px, 0px)'; 1774 | transformContainer(translate3d, false); 1775 | } 1776 | else { 1777 | container.css("top", -top); 1778 | } 1779 | } 1780 | 1781 | function getTransforms(translate3d){ 1782 | return { 1783 | '-webkit-transform': translate3d, 1784 | '-moz-transform': translate3d, 1785 | '-ms-transform':translate3d, 1786 | 'transform': translate3d 1787 | }; 1788 | } 1789 | 1790 | function setIsScrollable(value, direction){ 1791 | switch (direction){ 1792 | case 'up': isScrollAllowed.up = value; break; 1793 | case 'down': isScrollAllowed.down = value; break; 1794 | case 'left': isScrollAllowed.left = value; break; 1795 | case 'right': isScrollAllowed.right = value; break; 1796 | case 'all': $.fn.fullpage.setAllowScrolling(value); 1797 | } 1798 | } 1799 | 1800 | 1801 | /* 1802 | * Destroys fullpage.js plugin events and optinally its html markup and styles 1803 | */ 1804 | $.fn.fullpage.destroy = function(all){ 1805 | $.fn.fullpage.setAutoScrolling(false, 'internal'); 1806 | $.fn.fullpage.setAllowScrolling(false); 1807 | $.fn.fullpage.setKeyboardScrolling(false); 1808 | 1809 | 1810 | $(window) 1811 | .off('scroll', scrollHandler) 1812 | .off('hashchange', hashChangeHandler) 1813 | .off('resize', resizeHandler); 1814 | 1815 | $(document) 1816 | .off('click', '#fp-nav a') 1817 | .off('mouseenter', '#fp-nav li') 1818 | .off('mouseleave', '#fp-nav li') 1819 | .off('click', '.fp-slidesNav a') 1820 | .off('mouseover', options.normalScrollElements) 1821 | .off('mouseout', options.normalScrollElements); 1822 | 1823 | $('.fp-section') 1824 | .off('click', '.fp-controlArrow'); 1825 | 1826 | //lets make a mess! 1827 | if(all){ 1828 | destroyStructure(); 1829 | } 1830 | }; 1831 | 1832 | /* 1833 | * Removes inline styles added by fullpage.js 1834 | */ 1835 | function destroyStructure(){ 1836 | //reseting the `top` or `translate` properties to 0 1837 | silentScroll(0); 1838 | 1839 | $('#fp-nav, .fp-slidesNav, .fp-controlArrow').remove(); 1840 | 1841 | //removing inline styles 1842 | $('.fp-section').css( { 1843 | 'height': '', 1844 | 'background-color' : '', 1845 | 'padding': '' 1846 | }); 1847 | 1848 | $('.fp-slide').css( { 1849 | 'width': '' 1850 | }); 1851 | 1852 | container.css({ 1853 | 'height': '', 1854 | 'position': '', 1855 | '-ms-touch-action': '', 1856 | 'touch-action': '' 1857 | }); 1858 | 1859 | //removing added classes 1860 | $('.fp-section, .fp-slide').each(function(){ 1861 | removeSlimScroll($(this)); 1862 | $(this).removeClass('fp-table active'); 1863 | }); 1864 | 1865 | removeAnimation(container); 1866 | removeAnimation(container.find('.fp-easing')); 1867 | 1868 | //Unwrapping content 1869 | container.find('.fp-tableCell, .fp-slidesContainer, .fp-slides').each(function(){ 1870 | //unwrap not being use in case there's no child element inside and its just text 1871 | $(this).replaceWith(this.childNodes); 1872 | }); 1873 | 1874 | //scrolling the page to the top with no animation 1875 | $('html, body').scrollTop(0); 1876 | } 1877 | 1878 | /* 1879 | * Sets the state for a variable with multiple states (original, and temporal) 1880 | * Some variables such as `autoScrolling` or `recordHistory` might change automatically its state when using `responsive` or `autoScrolling:false`. 1881 | * This function is used to keep track of both states, the original and the temporal one. 1882 | * If type is not 'internal', then we assume the user is globally changing the variable. 1883 | */ 1884 | function setVariableState(variable, value, type){ 1885 | options[variable] = value; 1886 | if(type !== 'internal'){ 1887 | originals[variable] = value; 1888 | } 1889 | } 1890 | 1891 | /** 1892 | * Displays warnings 1893 | */ 1894 | function displayWarnings(){ 1895 | // Disable mutually exclusive settings 1896 | if (options.continuousVertical && 1897 | (options.loopTop || options.loopBottom)) { 1898 | options.continuousVertical = false; 1899 | showError('warn', "Option `loopTop/loopBottom` is mutually exclusive with `continuousVertical`; `continuousVertical` disabled"); 1900 | } 1901 | if(options.continuousVertical && options.scrollBar){ 1902 | options.continuousVertical = false; 1903 | showError('warn', "Option `scrollBar` is mutually exclusive with `continuousVertical`; `continuousVertical` disabled"); 1904 | } 1905 | 1906 | //anchors can not have the same value as any element ID or NAME 1907 | $.each(options.anchors, function(index, name){ 1908 | if($('#' + name).length || $('[name="'+name+'"]').length ){ 1909 | showError('error', "data-anchor tags can not have the same value as any `id` element on the site (or `name` element for IE)."); 1910 | } 1911 | }); 1912 | } 1913 | 1914 | function showError(type, text){ 1915 | console && console[type] && console[type]('fullPage: ' + text); 1916 | } 1917 | }; 1918 | })(jQuery); 1919 | -------------------------------------------------------------------------------- /flask_jianshu/static/js/jquery.fullPage.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * fullPage 2.5.4 3 | * https://github.com/alvarotrigo/fullPage.js 4 | * MIT licensed 5 | * 6 | * Copyright (C) 2013 alvarotrigo.com - A project by Alvaro Trigo 7 | */ 8 | (function(b){b.fn.fullpage=function(c){function pa(a){a.find(".fp-slides").after('
      ');"#fff"!=c.controlArrowColor&&(a.find(".fp-controlArrow.fp-next").css("border-color","transparent transparent transparent "+c.controlArrowColor),a.find(".fp-controlArrow.fp-prev").css("border-color","transparent "+c.controlArrowColor+" transparent transparent"));c.loopHorizontal||a.find(".fp-controlArrow.fp-prev").hide()}function qa(){b("body").append('
        '); 9 | k=b("#fp-nav");k.css("color",c.navigationColor);k.addClass(c.navigationPosition);for(var a=0;a',e=c.navigationTooltips[a];void 0!=e&&""!=e&&(d+='
        '+e+"
        ");d+="";k.find("ul").append(d)}}function R(){b(".fp-section").each(function(){var a=b(this).find(".fp-slide");a.length?a.each(function(){y(b(this))}):y(b(this))});b.isFunction(c.afterRender)&& 10 | c.afterRender.call(this)}function S(){if(!c.autoScrolling||c.scrollBar){var a=b(window).scrollTop(),d=0,e=Math.abs(a-b(".fp-section").first().offset().top);b(".fp-section").each(function(c){var f=Math.abs(a-b(this).offset().top);f=d[0].scrollHeight:void 0,c)f();else return!0;else f()}}function sa(a){var d=a.originalEvent;if(!W(a.target)){c.autoScrolling&&!c.scrollBar&&a.preventDefault();a=b(".fp-section.active");var e=V(a);m||t||(d=X(d),u=d.y,A=d.x,a.find(".fp-slides").length&&Math.abs(B-A)>Math.abs(v-u)?Math.abs(B-A)>b(window).width()/100*c.touchSensitivity&& 13 | (B>A?l.right&&b.fn.fullpage.moveSlideRight():l.left&&b.fn.fullpage.moveSlideLeft()):c.autoScrolling&&!c.scrollBar&&Math.abs(v-u)>b(window).height()/100*c.touchSensitivity&&(v>u?z("down",e):u>v&&z("up",e)))}}function W(a,d){d=d||0;var e=b(a).parent();return dd?z("down",a):z("up",a));return!1}}function Y(a){var d=b(".fp-section.active").find(".fp-slides");if(d.length&&!t){var e=d.find(".fp-slide.active"),f=null,f="prev"===a?e.prev(".fp-slide"):e.next(".fp-slide");if(!f.length){if(!c.loopHorizontal)return;f="prev"===a?e.siblings(":last"):e.siblings(":first")}t=!0;w(d,f)}}function Z(){b(".fp-slide.active").each(function(){J(b(this))})} 15 | function q(a,d,e){var f=a.position();if("undefined"!==typeof f&&(d={element:a,callback:d,isMovementUp:e,dest:f,dtop:f.top,yMovement:G(a),anchorLink:a.data("anchor"),sectionIndex:a.index(".fp-section"),activeSlide:a.find(".fp-slide.active"),activeSection:b(".fp-section.active"),leavingSection:b(".fp-section.active").index(".fp-section")+1,localIsResizing:x},!(d.activeSection.is(a)&&!x||c.scrollBar&&b(window).scrollTop()===d.dtop))){if(d.activeSlide.length)var g=d.activeSlide.data("anchor"),h=d.activeSlide.index(); 16 | c.autoScrolling&&c.continuousVertical&&"undefined"!==typeof d.isMovementUp&&(!d.isMovementUp&&"up"==d.yMovement||d.isMovementUp&&"down"==d.yMovement)&&(d.isMovementUp?b(".fp-section.active").before(d.activeSection.nextAll(".fp-section")):b(".fp-section.active").after(d.activeSection.prevAll(".fp-section").get().reverse()),n(b(".fp-section.active").position().top),Z(),d.wrapAroundElements=d.activeSection,d.dest=d.element.position(),d.dtop=d.dest.top,d.yMovement=G(d.element));a.addClass("active").siblings().removeClass("active"); 17 | m=!0;I(h,g,d.anchorLink,d.sectionIndex);b.isFunction(c.onLeave)&&!d.localIsResizing&&c.onLeave.call(this,d.leavingSection,d.sectionIndex+1,d.yMovement);ua(d);p=d.anchorLink;c.autoScrolling&&H(d.anchorLink,d.sectionIndex)}}function ua(a){if(c.css3&&c.autoScrolling&&!c.scrollBar)aa("translate3d(0px, -"+a.dtop+"px, 0px)",!0),setTimeout(function(){ba(a)},c.scrollingSpeed);else{var d=va(a);b(d.element).animate(d.options,c.scrollingSpeed,c.easing).promise().done(function(){ba(a)})}}function va(a){var b= 18 | {};c.autoScrolling&&!c.scrollBar?(b.options={top:-a.dtop},b.element="."+ca):(b.options={scrollTop:a.dtop},b.element="html, body");return b}function wa(a){a.wrapAroundElements&&a.wrapAroundElements.length&&(a.isMovementUp?b(".fp-section:first").before(a.wrapAroundElements):b(".fp-section:last").after(a.wrapAroundElements),n(b(".fp-section.active").position().top),Z())}function ba(a){wa(a);b.isFunction(c.afterLoad)&&!a.localIsResizing&&c.afterLoad.call(this,a.anchorLink,a.sectionIndex+1);setTimeout(function(){m= 19 | !1;b.isFunction(a.callback)&&a.callback.call(this)},600)}function da(){if(!F){var a=window.location.hash.replace("#","").split("/"),b=a[0],a=a[1];if(b.length){var c="undefined"===typeof p,f="undefined"===typeof p&&"undefined"===typeof a&&!t;(b&&b!==p&&!c||f||!t&&K!=a)&&L(b,a)}}}function w(a,d){var e=d.position(),f=a.find(".fp-slidesContainer").parent(),g=d.index(),h=a.closest(".fp-section"),k=h.index(".fp-section"),l=h.data("anchor"),n=h.find(".fp-slidesNav"),m=d.data("anchor"),q=x;if(c.onSlideLeave){var p= 20 | h.find(".fp-slide.active").index(),r;r=p==g?"none":p>g?"left":"right";q||"none"===r||b.isFunction(c.onSlideLeave)&&c.onSlideLeave.call(this,l,k+1,p,r)}d.addClass("active").siblings().removeClass("active");"undefined"===typeof m&&(m=g);!c.loopHorizontal&&c.controlArrows&&(h.find(".fp-controlArrow.fp-prev").toggle(0!=g),h.find(".fp-controlArrow.fp-next").toggle(!d.is(":last-child")));h.hasClass("active")&&I(g,m,l,k);var u=function(){q||b.isFunction(c.afterSlideLoad)&&c.afterSlideLoad.call(this,l,k+ 21 | 1,m,g);t=!1};c.css3?(e="translate3d(-"+e.left+"px, 0px, 0px)",ea(a.find(".fp-slidesContainer"),020*Math.max(M,a)/100&&(b.fn.fullpage.reBuild(!0), 22 | M=a)}}else clearTimeout(ia),ia=setTimeout(function(){b.fn.fullpage.reBuild(!0)},500)}function ha(){if(c.responsive){var a=g.hasClass("fp-responsive");b(window).width()a||900>d){var c=Math.min(100*a/825,100*d/900).toFixed(2);b("body").css("font-size",c+"%")}else b("body").css("font-size","100%")}function H(a,d){c.menu&&(b(c.menu).find(".active").removeClass("active"),b(c.menu).find('[data-menuanchor="'+a+'"]').addClass("active"));c.navigation&&(b("#fp-nav").find(".active").removeClass("active"),a?b("#fp-nav").find('a[href="#'+a+'"]').addClass("active"):b("#fp-nav").find("li").eq(d).find("a").addClass("active"))} 24 | function G(a){var d=b(".fp-section.active").index(".fp-section");a=a.index(".fp-section");return d==a?"none":d>a?"up":"down"}function y(a){a.css("overflow","hidden");var b=a.closest(".fp-section"),e=a.find(".fp-scrollable");if(e.length)var f=e.get(0).scrollHeight;else f=a.get(0).scrollHeight,c.verticalCentered&&(f=a.find(".fp-tableCell").get(0).scrollHeight);b=h-parseInt(b.css("padding-bottom"))-parseInt(b.css("padding-top"));f>b?e.length?e.css("height",b+"px").parent().css("height",b+"px"):(c.verticalCentered? 25 | a.find(".fp-tableCell").wrapInner('
        '):a.wrapInner('
        '),a.find(".fp-scrollable").slimScroll({allowPageScroll:!0,height:b+"px",size:"10px",alwaysVisible:!0})):ja(a);a.css("overflow","")}function ja(a){a.find(".fp-scrollable").children().first().unwrap().unwrap();a.find(".slimScrollBar").remove();a.find(".slimScrollRail").remove()}function ka(a){a.addClass("fp-table").wrapInner('
        ')}function la(a){var b= 26 | h;if(c.paddingTop||c.paddingBottom)b=a,b.hasClass("fp-section")||(b=a.closest(".fp-section")),a=parseInt(b.css("padding-top"))+parseInt(b.css("padding-bottom")),b=h-a;return b}function aa(a,b){b?ea(g):O(g);g.css(fa(a));setTimeout(function(){g.removeClass("fp-notransition")},10)}function L(a,d){"undefined"===typeof d&&(d=0);var c=isNaN(a)?b('[data-anchor="'+a+'"]'):b(".fp-section").eq(a-1);a===p||c.hasClass("active")?ma(c,d):q(c,function(){ma(c,d)})}function ma(a,b){if("undefined"!=typeof b){var c= 27 | a.find(".fp-slides"),f=c.find('[data-anchor="'+b+'"]');f.length||(f=c.find(".fp-slide").eq(b));f.length&&w(c,f)}}function ya(a,b){a.append('
          ');var e=a.find(".fp-slidesNav");e.addClass(c.slidesNavPosition);for(var f=0;f');e.css("margin-left","-"+e.width()/2+"px");e.find("li").first().find("a").addClass("active")}function I(a,b,e,f){var g="";c.anchors.length?(a?("undefined"!==typeof e&&(g=e),"undefined"=== 28 | typeof b&&(b=a),K=b,na(g+"/"+b)):("undefined"!==typeof a&&(K=b),na(e)),D(location.hash)):"undefined"!==typeof a?D(f+"-"+a):D(String(f))}function na(a){if(c.recordHistory)location.hash=a;else if(C||P)history.replaceState(void 0,void 0,"#"+a);else{var b=window.location.href.split("#")[0];window.location.replace(b+"#"+a)}}function D(a){a=a.replace("/","-").replace("#","");b("body")[0].className=b("body")[0].className.replace(/\b\s?fp-viewing-[^\s]+\b/g,"");b("body").addClass("fp-viewing-"+a)}function za(){var a= 29 | document.createElement("p"),b,c={webkitTransform:"-webkit-transform",OTransform:"-o-transform",msTransform:"-ms-transform",MozTransform:"-moz-transform",transform:"transform"};document.body.insertBefore(a,null);for(var f in c)void 0!==a.style[f]&&(a.style[f]="translate3d(1px,1px,1px)",b=window.getComputedStyle(a).getPropertyValue(c[f]));document.body.removeChild(a);return void 0!==b&&0');e.parent().wrap('
          ');b(this).find(".fp-slidesContainer").css("width",a+"%");c.controlArrows&&pa(b(this));c.slidesNavigation&&ya(b(this),f);e.each(function(a){b(this).css("width", 44 | g+"%");c.verticalCentered&&ka(b(this))});d=d.find(".fp-slide.active");0==d.length?e.eq(0).addClass("active"):J(d)}else c.verticalCentered&&ka(b(this))}).promise().done(function(){b.fn.fullpage.setAutoScrolling(c.autoScrolling,"internal");var a=b(".fp-section.active").find(".fp-slide.active");a.length&&(0!=b(".fp-section.active").index(".fp-section")||0==b(".fp-section.active").index(".fp-section")&&0!=a.index())&&J(a);c.fixedElements&&c.css3&&b(c.fixedElements).appendTo("body");c.navigation&&(k.css("margin-top", 45 | "-"+k.height()/2+"px"),k.find("li").eq(b(".fp-section.active").index(".fp-section")).find("a").addClass("active"));c.menu&&c.css3&&b(c.menu).closest(".fullpage-wrapper").length&&b(c.menu).appendTo("body");c.scrollOverflow?("complete"===document.readyState&&R(),b(window).on("load",R)):b.isFunction(c.afterRender)&&c.afterRender.call(this);ha();a=window.location.hash.replace("#","").split("/")[0];if(a.length){var d=b('[data-anchor="'+a+'"]');!c.animateAnchor&&d.length&&(c.autoScrolling?n(d.position().top): 46 | (n(0),D(a),b("html, body").scrollTop(d.position().top)),H(a,null),b.isFunction(c.afterLoad)&&c.afterLoad.call(this,a,d.index(".fp-section")+1),d.addClass("active").siblings().removeClass("active"))}b(window).on("load",function(){var a=window.location.hash.replace("#","").split("/"),b=a[0],a=a[1];b&&L(b,a)})});var T,U,F=!1;b(window).on("scroll",S);var v=0,B=0,u=0,A=0;b(window).on("hashchange",da);b(document).keydown(function(a){if(c.keyboardScrolling&&c.autoScrolling&&(40!=a.which&&38!=a.which||a.preventDefault(), 47 | !m))switch(a.which){case 38:case 33:b.fn.fullpage.moveSectionUp();break;case 40:case 34:b.fn.fullpage.moveSectionDown();break;case 36:b.fn.fullpage.moveTo(1);break;case 35:b.fn.fullpage.moveTo(b(".fp-section").length);break;case 37:b.fn.fullpage.moveSlideLeft();break;case 39:b.fn.fullpage.moveSlideRight()}});b(document).on("click touchstart","#fp-nav a",function(a){a.preventDefault();a=b(this).parent().index();q(b(".fp-section").eq(a))});b(document).on("click touchstart",".fp-slidesNav a",function(a){a.preventDefault(); 48 | a=b(this).closest(".fp-section").find(".fp-slides");var c=a.find(".fp-slide").eq(b(this).closest("li").index());w(a,c)});c.normalScrollElements&&(b(document).on("mouseenter",c.normalScrollElements,function(){b.fn.fullpage.setMouseWheelScrolling(!1)}),b(document).on("mouseleave",c.normalScrollElements,function(){b.fn.fullpage.setMouseWheelScrolling(!0)}));b(".fp-section").on("click touchstart",".fp-controlArrow",function(){b(this).hasClass("fp-prev")?b.fn.fullpage.moveSlideLeft():b.fn.fullpage.moveSlideRight()}); 49 | b(window).resize(ga);var M=h,ia;b.fn.fullpage.destroy=function(a){b.fn.fullpage.setAutoScrolling(!1,"internal");b.fn.fullpage.setAllowScrolling(!1);b.fn.fullpage.setKeyboardScrolling(!1);b(window).off("scroll",S).off("hashchange",da).off("resize",ga);b(document).off("click","#fp-nav a").off("mouseenter","#fp-nav li").off("mouseleave","#fp-nav li").off("click",".fp-slidesNav a").off("mouseover",c.normalScrollElements).off("mouseout",c.normalScrollElements);b(".fp-section").off("click",".fp-controlArrow"); 50 | a&&Aa()}}})(jQuery); -------------------------------------------------------------------------------- /flask_jianshu/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 我在简书 6 | 79 | 80 | 81 |
          82 |
          83 |
          84 |

          85 |
          86 | 87 | 88 | {% if error_msg %} 89 |

          {{ error_msg }}

          90 | {% else %} 91 |
          92 | {% endif %} 93 | 94 | 95 | 96 |
          97 |
          98 |
          99 | 100 | -------------------------------------------------------------------------------- /flask_jianshu/templates/timeline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 我在简书 8 | 9 | 10 | 11 | 12 | 13 | 20 | 21 | 22 | 23 | 24 |
          25 | 26 |
          27 |
          28 | 29 |

          您好! {{ baseinfo['nickname'] }}

          30 |

          截止至 {{ baseinfo['update_time'] }}

          31 |

          您在简书关注了{{ baseinfo['like_users_num'] }}个用户, 32 | 拥有粉丝{{ baseinfo['followers_num'] }}个。

          33 |

          发表文章{{ baseinfo['share_notes_num'] }}篇, 34 | 写下文字{{ baseinfo['words_num'] }}个, 35 | 文章收获喜欢{{ baseinfo['be_liked_num'] }}个, 36 | 喜欢文章{{ baseinfo['like_notes_num'] }}

          37 |

          关注专题{{ baseinfo['like_colls_num'] }}个, 38 | 关注文集{{ baseinfo['like_nbs_num'] }}个。

          39 |

          发表评论{{ baseinfo['comment_notes_num'] }}次, 40 | 点赞别人评论{{ baseinfo['like_comments_num'] }}次。

          41 |

          打赏文章{{ baseinfo['reward_notes_num'] }}

          42 |
          43 |
          44 | 45 |
          46 |
          47 |

          加入简书以来的第一次



          48 |

          您于{{ first_tag_time['join_time'] }} 注册,加入简书

          49 | {% if first_tag_time['first_like_user'] %} 50 |

          51 | {{ first_tag_time['first_like_user']['time']|safe }} 52 |   53 | 第一次关注用户 54 |

          55 | {% endif %} 56 | {% if first_tag_time['first_share_note'] %} 57 |

          58 | {{ first_tag_time['first_share_note']['time']|safe }} 59 |   60 | 第一次发表文章 61 |

          62 | {% endif %} 63 | {% if first_tag_time['first_like_note'] %} 64 |

          65 | {{ first_tag_time['first_like_note']['time']|safe }} 66 |   67 | 第一次喜欢文章 68 |

          69 | {% endif %} 70 | {% if first_tag_time['first_like_coll'] %} 71 |

          72 | {{ first_tag_time['first_like_coll']['time']|safe }} 73 |   74 | 第一次关注专题 75 |

          76 | {% endif %} 77 | {% if first_tag_time['first_like_nb'] %} 78 |

          79 | {{ first_tag_time['first_like_nb']['time']|safe }} 80 |   81 | 第一次关注文集 82 |

          83 | {% endif %} 84 | {% if first_tag_time['first_comment'] %} 85 |

          86 | {{ first_tag_time['first_comment']['time']|safe }} 87 |  第一次发表评论: 88 | {{ first_tag_time['first_comment']['comment_text']|safe }} 89 |

          90 | {% endif %} 91 | {% if first_tag_time['first_like_comment'] %} 92 |

          93 | {{ first_tag_time['first_like_comment']['time']|safe }} 94 |  第一次赞了评论: 95 | {{ first_tag_time['first_like_comment']['comment_text']|safe }} 96 |

          97 | {% endif %} 98 | {% if first_tag_time['first_reward_note'] %} 99 |

          100 | {{ first_tag_time['first_reward_note']['time']|safe }} 101 |   102 | 第一次打赏文章 103 |

          104 | {% endif %} 105 | 106 |
          107 |
          108 | 109 |
          110 | 111 |
          112 | 163 |
          164 | 165 |
          166 | 167 |
          168 | 244 |
          245 | 246 |
          247 | 248 |
          249 | 311 |
          312 | 313 |
          314 | 315 |
          316 | 368 |
          369 | 370 |
          371 | 372 |
          373 | 443 |
          444 | 445 |
          446 | 447 |
          448 | 524 |
          525 | 526 | {% if week_hour['share_notes'] %} 527 |
          528 |
          529 |

          一周中发表文章时间频率分布

          530 | 531 |
          532 | 599 |
          600 |
          601 | {% endif %} 602 | 603 | {% if week_hour['like_notes'] %} 604 |
          605 |
          606 |

          一周中喜欢文章时间频率分布

          607 | 608 |
          609 | 676 |
          677 |
          678 | {% endif %} 679 | 680 | {% if week_hour['like_users'] %} 681 |
          682 |
          683 |

          一周中关注用户时间频率分布

          684 | 685 |
          686 | 753 |
          754 |
          755 | {% endif %} 756 | 757 | {% if week_hour['comment_notes'] %} 758 |
          759 |
          760 |

          一周中发表评论时间频率分布

          761 | 762 |
          763 | 830 |
          831 |
          832 | {% endif %} 833 | 834 | {% if week_hour['reward_notes'] %} 835 |
          836 |
          837 |

          一周中打赏时间频率分布

          838 | 839 |
          840 | 907 |
          908 |
          909 | {% endif %} 910 | 911 |
          912 |
          913 |

          共写下评论 {{ comments_num }} 条,以下词语出现频率较高

          914 | {{ wordcloud_chart|safe }} 915 |
          916 |
          917 | 918 |
          919 |
          920 |

          谢谢观赏

          921 |

          ----------end----------

          922 |
          923 |
          924 | 925 |
          926 | 927 | -------------------------------------------------------------------------------- /flask_jianshu/user_analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malone6/Jianshu/5dba08f03c02643457c5f2428f7c0d8384b53f31/flask_jianshu/user_analysis/__init__.py -------------------------------------------------------------------------------- /flask_jianshu/user_analysis/anlysis_timeline.py: -------------------------------------------------------------------------------- 1 | import pymongo 2 | import jieba 3 | from .jianshu_timeline import GetAllInfo 4 | from . import config 5 | from collections import Counter 6 | from datetime import datetime 7 | 8 | 9 | class AnalysisUser(): 10 | 11 | def __init__(self, slug,*args): 12 | self.client = pymongo.MongoClient(config.MONGO_HOST, config.MONGO_PORT) 13 | self.db = self.client[config.MONGO_TABLE] 14 | self.slug = slug 15 | self.parent_tags = args 16 | self.zh_parent_tags = ['发表评论','喜欢文章','赞赏文章','发表文章','关注用户','关注专题','点赞评论','关注文集'] 17 | 18 | user_data = self.db.user_timeline.find_one({'slug': self.slug}) 19 | update = config.UPDATE 20 | if user_data and update==False: 21 | '''如果指定不更新数据且数据已经在mongodb中''' 22 | self.user_data = user_data 23 | else: 24 | '''获取或更新数据到mongodb中''' 25 | GetAllInfo().getallinfo(slug) 26 | '''从mongodb中取出该用户的数据''' 27 | self.user_data = self.db.user_timeline.find_one({'slug': self.slug}) 28 | 29 | 30 | def get_baseinfo(self): 31 | baseinfo = {'head_pic':self.user_data['head_pic'], 32 | 'nickname':self.user_data['nickname'], 33 | 'update_time':self.user_data['update_time'], 34 | 'like_users_num':self.user_data['following_num'], 35 | 'followers_num':self.user_data['followers_num'], 36 | 'share_notes_num':self.user_data['articles_num'], 37 | 'words_num':self.user_data['words_num'], 38 | 'be_liked_num':self.user_data['be_liked_num'], 39 | 'like_notes_num':len(self.user_data['like_notes']), 40 | 'like_colls_num':len(self.user_data['like_colls']), 41 | 'like_nbs_num':len(self.user_data['like_notebooks']), 42 | 'comment_notes_num':len(self.user_data['comment_notes']), 43 | 'like_comments_num':len(self.user_data['like_comments']), 44 | 'reward_notes_num':len(self.user_data['reward_notes']) 45 | } 46 | # print(baseinfo) 47 | return baseinfo 48 | 49 | def get_first_tag_time(self): 50 | first_tag_time = { 51 | 'join_time': self.user_data['join_time'], 52 | 'first_like_user': extract_first_tag_time(self.user_data['like_users']), 53 | 'first_share_note': extract_first_tag_time(self.user_data['share_notes']), 54 | 'first_like_note': extract_first_tag_time(self.user_data['like_notes']), 55 | 'first_like_coll': extract_first_tag_time(self.user_data['like_colls']), 56 | 'first_like_nb': extract_first_tag_time(self.user_data['like_notebooks']), 57 | 'first_comment': extract_first_tag_time(self.user_data['comment_notes']), 58 | 'first_like_comment': extract_first_tag_time(self.user_data['like_comments']), 59 | 'first_reward_note': extract_first_tag_time(self.user_data['reward_notes']), 60 | } 61 | return first_tag_time 62 | 63 | 64 | def tags_data(self): 65 | tags_name = [{'name':name} for name in self.zh_parent_tags] 66 | tags_value = [{'value':len(self.user_data[tag])} for tag in self.parent_tags] 67 | tags_data = [dict(tags_name[i],**tags_value[i]) for i in range(len(tags_name))] 68 | # print(tags_data) 69 | return tags_data 70 | 71 | def all_tags_time(self): 72 | '''all_tags = ('comment_notes', 'like_notes', 'reward_notes','share_notes', 73 | 'like_users', 'like_colls','like_comments', 'like_notebooks')''' 74 | all_time_list = [] 75 | for tag in self.parent_tags: 76 | pertime = [each['time'] for each in self.user_data['%s'%tag]] 77 | all_time_list.extend(pertime) 78 | # 加入简书的时间 79 | join_time = self.user_data['join_time'] 80 | if join_time==None: 81 | pass 82 | else: 83 | all_time_list.append(join_time) 84 | return all_time_list 85 | 86 | def per_tag_time(self, tag): 87 | ''' 88 | :param tag: 标签,动态的类型 89 | :return: 子注册以来,发生该种类型动态的所有时间点 90 | ''' 91 | per_tag_time = [each['time'] for each in self.user_data['%s' % tag]] 92 | return per_tag_time 93 | 94 | def all_tags_data(self,time_period): 95 | ''' 96 | 根据选择对的时间段,得出注册以来所有动态在时间段内的分布统计 97 | :param time_period: 时间段。可选'month','week','day','hour'四种类型,分别以月,周,天,小时分类 98 | :return: 99 | ''' 100 | if time_period == 'month': 101 | all_tags_data = extract_month_data(self.all_tags_time()) 102 | elif time_period == 'week': 103 | all_tags_data = extract_week_data(self.all_tags_time()) 104 | elif time_period == 'day': 105 | all_tags_data = extract_day_data(self.all_tags_time()) 106 | elif time_period == 'hour': 107 | all_tags_data = extract_hour_data(self.all_tags_time()) 108 | else: 109 | all_tags_data = None 110 | # print(all_tags_data) 111 | return all_tags_data 112 | 113 | 114 | def get_comment(self): 115 | ''' 116 | 抽出所有评论,进行词频统计,得出该用户评论中最常用的词,并绘制词云 117 | :return: 118 | ''' 119 | comments = self.user_data['comment_notes'] 120 | text = [c['comment_text'] for c in comments] 121 | comm_text = ''.join(text) 122 | comm_text_tmp = jieba.cut(comm_text, 50) 123 | word_list = list(word for word in comm_text_tmp) 124 | freq = Counter(word_list).most_common(150) 125 | comm_word = {x[0]: x[1] for x in freq if len(x[0])>=2} 126 | # hot_comments = [{'name':list(comm_word.keys())[i],'value':list(comm_word.values())[i]} 127 | # for i in range(len(comm_word))] 128 | return [len(text),comm_word] 129 | 130 | 131 | def one_tag_data(self,tag,time_period): 132 | ''' 133 | 单种动态类型的情况统计 134 | :param tag: 需要的动态类型,tag可选值可在config.TIMELINE_TYPES中查看,分别对应中文self.zh_parent_tags 135 | :param time_period: 时间段。可选'month','week','day','hour'四种类型,分别以月,周,天,小时分类 136 | :return: 动态类型和统计情况组成的列表 137 | ''' 138 | if time_period == 'month': 139 | one_tag_data = extract_month_data(self.per_tag_time(tag)) 140 | elif time_period == 'week': 141 | one_tag_data = extract_week_data(self.per_tag_time(tag)) 142 | elif time_period == 'day': 143 | one_tag_data = extract_day_data(self.per_tag_time(tag)) 144 | elif time_period == 'hour': 145 | one_tag_data = extract_hour_data(self.per_tag_time(tag)) 146 | else: 147 | one_tag_data = None 148 | 149 | zh_tag_type = self.zh_parent_tags[self.parent_tags.index(tag)] 150 | # print(zh_tag_type,one_tag_data) 151 | 152 | return [zh_tag_type,one_tag_data] 153 | 154 | def tag_week_hour_data(self,*args): 155 | tag_time_list = [] 156 | for tag in args: 157 | per_time_list = self.per_tag_time(tag) 158 | tag_time_list.extend(per_time_list) 159 | week_hour_list = [] 160 | for time_str in tag_time_list: 161 | week_hour = date_to_week(time_str)[0]+time_str[11:13] 162 | week_hour_list.append(week_hour) 163 | counter_week_hour = Counter(week_hour_list) 164 | # print(counter_week_hour) 165 | max_freq = counter_week_hour.most_common(1)[0][1] 166 | tag_week_hour_data = [] 167 | for x in counter_week_hour.items(): 168 | '''x = ('412',7) 转换成each=[周几,几点,频率]''' 169 | each = [int(x[0][0]),int(x[0][1:3]),x[1]] 170 | tag_week_hour_data.append(each) 171 | # print(tag_week_hour_data) 172 | return [max_freq,tag_week_hour_data] 173 | 174 | 175 | 176 | '''以下为辅助函数,用来时间提取及统计''' 177 | def extract_first_tag_time(data): 178 | ''' 179 | 根据给出的data,以时间对列表进行排序,返回列表的第一个值,即为数据列表中的第一次动态 180 | :param data: 用户某个类型的动态列表,如所有关注用户数据 181 | {'like_users':[{'slug': 'y3Dbcz', 'time': '2013-05-24 18:26:01'}, 182 | {'slug': '2f1c0190679d', 'time': '2014-02-11 13:02:03'}]} 183 | :return: 数据列表中的第一次动态 184 | ''' 185 | if data: 186 | sorted_data = sorted(data,key=lambda each: each['time']) 187 | first_tag_time = sorted_data[0] 188 | return first_tag_time 189 | else: 190 | return None 191 | 192 | def extract_time_func(time_list, start, end): 193 | ''' 194 | 根据给出的时间点列表,进行时间分片,和时间段统计 195 | :param time_list: 时间点列表 196 | :param start: 时间分片的起始点,例如1999-09-09 09:09:09,通过对此时间串分片得出月份:1999-09、小时:09时等 197 | :param end: 时间分片的起始点 198 | :return: 各时间段的动态频次 199 | ''' 200 | if time_list: 201 | user_time_you_want = [time[start:end] for time in time_list] 202 | counter_you_want_f = Counter(user_time_you_want) 203 | sort_counter = sorted(counter_you_want_f.items(), key=lambda t: t[0]) 204 | data = {'time_slot': [a[0] for a in sort_counter], 205 | 'freqency': [a[1] for a in sort_counter]} 206 | return data 207 | else: 208 | return None 209 | 210 | def extract_month_data(time_list): 211 | ''' 212 | 根据给出的得时间列表,以月为时间段,统计每月动态频次 213 | :param time_list: 时间列表 214 | :return: 215 | ''' 216 | if time_list: 217 | data = extract_time_func(time_list, 0, 7) 218 | month_data = {'month_line': data['time_slot'], 'month_freqency': data['freqency']} 219 | # print(month_data) 220 | return month_data 221 | else: 222 | return None 223 | 224 | def extract_day_data(time_list): 225 | '''函数功能同上''' 226 | if time_list: 227 | data = extract_time_func(time_list, 0, 10) 228 | day_data = {'day_line': data['time_slot'], 'day_freqency': data['freqency']} 229 | # print(day_data) 230 | return day_data 231 | else: 232 | return None 233 | 234 | def extract_week_data(time_list): 235 | if time_list: 236 | '''函数功能同上''' 237 | user_week_you_want = [date_to_week(x) for x in time_list] 238 | counter_you_want_f = Counter(user_week_you_want) 239 | sort_counter = sorted(counter_you_want_f.items(), key=lambda t: t[0]) 240 | week_data = {'week_line': [a[0][1:] for a in sort_counter], 241 | 'week_freqency': [a[1] for a in sort_counter]} 242 | # print(data) 243 | return week_data 244 | else: 245 | return None 246 | 247 | def date_to_week(string_date): 248 | ''' 249 | 通过datetime模块,将时间串转化为周 250 | :param string_date: 时间串列表 251 | :return: 周列表 252 | ''' 253 | time = datetime.strptime(string_date, '%Y-%m-%d %H:%M:%S') 254 | week_day = time.weekday() 255 | week_day_dict = {0: '0周一', 1: '1周二', 2: '2周三', 3: '3周四', 256 | 4: '4周五', 5: '5周六', 6: '6周日', } 257 | return week_day_dict[week_day] 258 | 259 | 260 | def extract_hour_data(time_list): 261 | '''函数功能同上''' 262 | data = extract_time_func(time_list, 11, 13) 263 | hour_data = {'hour_line': [x + ':00' for x in data['time_slot']], 'hour_freqency': data['freqency']} 264 | # print(hour_data) 265 | return hour_data 266 | 267 | 268 | if __name__ == '__main__': 269 | slug = 'yZq3ZV' 270 | args = config.TIMELINE_TYPES 271 | user = AnalysisUser(slug, *args) 272 | 273 | # tags_data = user.tags_data() 274 | # all_month_data = user.all_tags_data(time_period='month') 275 | # all_day_data = user.all_tags_data(time_period='day') 276 | # all_hour_data = user.all_tags_data(time_period='hour') 277 | # all_week_data = user.all_tags_data(time_period='week') 278 | # comments = user.get_comment() 279 | # share_month_data = user.one_tag_data(tag='like_notes',time_period='month') 280 | # share_week_hour = user.tag_week_hour_data(tag='share_notes') 281 | # baseinfo = user.get_baseinfo() 282 | first_tag_time = user.get_first_tag_time() 283 | 284 | 285 | -------------------------------------------------------------------------------- /flask_jianshu/user_analysis/config.py: -------------------------------------------------------------------------------- 1 | USER = 'default' 2 | START_TIME = 'register_time' 3 | END_TIME = 'yesterday' 4 | TIMELINE_TYPES = ('comment_notes', 'like_notes', 'reward_notes','share_notes', 5 | 'like_users', 'like_colls','like_comments', 'like_notebooks') 6 | 7 | MONGO_HOST = "localhost" 8 | MONGO_PORT = 27017 9 | MONGO_TABLE = 'JianShu' 10 | 11 | UPDATE = True -------------------------------------------------------------------------------- /flask_jianshu/user_analysis/jianshu_timeline.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from lxml import etree 3 | from fake_useragent import UserAgent 4 | import pymongo 5 | import time 6 | import sys 7 | from .config import * 8 | 9 | # 未做其他版本兼容。检查python版本,python3.5以上可用 10 | PY35 = sys.version_info.major == 3 and sys.version_info.minor >= 5 11 | if not PY35: 12 | raise Exception('请使用 Python3.5 及其以上的版本!') 13 | 14 | 15 | # 遇到递归深度过大导致栈溢出,坑。直接修改最大递归深度 16 | sys.setrecursionlimit(3000) 17 | # 修改python的最大递归深度,默认是998,超过后会栈溢出,由于需要,这里暂时把递归深度改成3000。 18 | # 另外,5000都不行,试了下,最大是3927,超过这个值,我的机器上的python就会停止工作。 19 | 20 | 21 | ''' 22 | # 抓取大量用户动态时,开启多线程,线程数在config中配置 23 | import concurrent.futures 24 | threadpool = concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREAD) 25 | ''' 26 | 27 | client = pymongo.MongoClient(MONGO_HOST, MONGO_PORT) 28 | db = client['JianShu'] 29 | 30 | def retry(attempt): 31 | ''' 32 | 函数重试装饰器 33 | :param attempt: 函数重试次数, 34 | 将该装饰器装饰于任何目标函数,目标函数执行时会进行给定次数的重试 35 | ''' 36 | def decorator(func): 37 | def wrapper(*args, **kw): 38 | att = 0 39 | while att < attempt: 40 | try: 41 | time.sleep(5) 42 | return func(*args, **kw) 43 | except Exception as e: 44 | att += 1 45 | print('第%s次出现了错误' % att, e) 46 | return wrapper 47 | return decorator 48 | 49 | 50 | BASE_HEADERS = { 51 | 'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4', 52 | 'Host': 'www.jianshu.com', 53 | 'Accept-Encoding': 'gzip, deflate, sdch', 54 | 'X-Requested-With': 'XMLHttpRequest', 55 | 'Accept': 'text/html, */*; q=0.01', 56 | 'User-Agent': UserAgent().random, 57 | 'Connection': 'keep-alive', 58 | 'Referer': 'http://www.jianshu.com', 59 | } 60 | 61 | 62 | class GetUserInfo(): 63 | def __init__(self, slug): 64 | self.headers = BASE_HEADERS 65 | self.slug = slug 66 | 67 | @retry(5) 68 | def get_base_info(self): 69 | url = 'http://www.jianshu.com/u/{slug}'.format(slug=self.slug) 70 | response = requests.get(url, headers=self.headers) 71 | if response.status_code == 404: 72 | '''经测试,出现404时都是因为用户被封禁或注销,即显示: 73 | 您要找的页面不存在.可能是因为您的链接地址有误、该文章已经被作者删除或转为私密状态。''' 74 | return None 75 | else: 76 | tree = etree.HTML(response.text) 77 | 78 | div_main_top = tree.xpath('//div[@class="main-top"]')[0] 79 | nickname = div_main_top.xpath('.//div[@class="title"]//a/text()')[0] 80 | head_pic = div_main_top.xpath('.//a[@class="avatar"]//img/@src')[0] 81 | div_main_top.xpath('.//div[@class="title"]//i/@class') 82 | 83 | # 检查用户填写的性别信息。1:男 -1:女 0:性别未填写 84 | if div_main_top.xpath('.//i[@class="iconfont ic-man"]'): gender = 1 85 | elif div_main_top.xpath('.//i[@class="iconfont ic-woman"]'): gender = -1 86 | else: gender = 0 87 | 88 | # 判断该用户是否为签约作者。is_contract为1是简书签约作者,为0则是普通用户 89 | if div_main_top.xpath('.//i[@class="iconfont ic-write"]'): is_contract = 1 90 | else: is_contract = 0 91 | 92 | # 取出用户文章及关注量 93 | info = div_main_top.xpath('.//li//p//text()') 94 | 95 | item = {'nickname': nickname, 96 | 'slug': self.slug, 97 | 'head_pic': head_pic, 98 | 'gender': gender, 99 | 'is_contract': is_contract, 100 | 'following_num': int(info[0]), 101 | 'followers_num': int(info[1]), 102 | 'articles_num': int(info[2]), 103 | 'words_num': int(info[3]), 104 | 'be_liked_num': int(info[4]), 105 | 'update_time': time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) 106 | } 107 | # 取当前抓取时间,为用户信息更新时间。添加update_time字段 108 | return item 109 | 110 | 111 | class GetUerTimeline(): 112 | def __init__(self, slug, update=False): 113 | ''' 114 | :param slug: 目标用户的slug(用户的唯一标识),如:我的是5a7aa9b0cf9e 115 | :param update: 默认update=False,是指该次调用是首次抓取。 116 | 当传参update=True时,表明该次调用是更新信息的抓取 117 | ''' 118 | self.slug = slug 119 | 120 | # 抓取动态采用不同headers,带"X-PJAX": "true"返回动态加载片段,加Referer反盗链。 121 | AJAX_HEADERS = {"Referer": "http//:www.jianshu.com/u/{slug}".format(slug=self.slug), 122 | "X-PJAX": "true"} 123 | self.headers = dict(BASE_HEADERS, **AJAX_HEADERS) 124 | 125 | # 初始化盛数据的容器:timeline 126 | self.timeline = { 127 | 'comment_notes': [], 128 | 'like_notes': [], 129 | 'reward_notes': [], 130 | 'share_notes': [], 131 | 'like_users': [], 132 | 'like_colls': [], 133 | 'like_comments': [], 134 | 'like_notebooks': [], 135 | } 136 | 137 | if update: 138 | # 如果要更新动态,取出数据库中的最新动态时间mongo_latest_time,以便与真实最新动态作对比 139 | self.mongo_latest_time = \ 140 | db.user_timeline.find_one({'slug': slug}, {'latest_time': 1}).get('latest_time') 141 | self.update = update 142 | 143 | def get_timeline(self, id=None, page=1): 144 | ''' 145 | :param id: 用户动态翻页时,后一页的请求需要id值。为:前一页的最后一条动态的class节点的id-1 146 | :param page: 翻页 147 | :return: 返回加入数据之后的self.timeline 148 | ''' 149 | if id == None: 150 | # 动态第一页 151 | url = 'http://www.jianshu.com/users/{slug}/timeline'.format(slug=self.slug) 152 | else: 153 | # 动态第二页之后需要依赖值id,可从前一页中取数据 154 | url = 'http://www.jianshu.com/users/{slug}/timeline?max_id={id}&page={page}' \ 155 | .format(slug=self.slug, id=id, page=page) 156 | print(url) 157 | 158 | response = requests.get(url, headers=self.headers) 159 | tree = etree.HTML(response.text) 160 | li_tags = tree.xpath('//li') 161 | if li_tags: 162 | # 抽取出最新动态时间 163 | latest_time = li_tags[0].xpath('//@data-datetime')[0].split('+')[0].replace('T', ' ') 164 | if self.update == True: 165 | if latest_time == self.mongo_latest_time: 166 | # 如果经检查,数据库中就是最新动态。则不用继续抓取 167 | return self.timeline 168 | 169 | if page == 1: 170 | # 无论是否更新(避免多余判断),取出最新动态时间,更新到数据库 171 | self.timeline['latest_time'] = latest_time 172 | print(latest_time) 173 | 174 | # 遍历页面中所有动态,进行逐条解析 175 | for li in li_tags: 176 | # 该条动态时间点 177 | mark_time = self.get_mark_time(li) 178 | if self.update == True: 179 | # 更新动态时,如果解析到数据库已存的时间点时则停止解析,否则继续 180 | if mark_time == self.mongo_latest_time: 181 | return self.timeline 182 | else: 183 | self.parse_li(li, mark_time) 184 | else: 185 | self.parse_li(li, mark_time) 186 | # 抽取并计算得到下一页动态依赖的id 187 | last_li_id = li_tags[-1].xpath('@id')[0] 188 | next_first_id = int(last_li_id.split('-')[1]) - 1 189 | return self.get_timeline(next_first_id, page + 1) 190 | else: 191 | # 页面为空,没更多的动态了 192 | return self.timeline 193 | 194 | def parse_li(self, li, mark_time): 195 | ''' 196 | :param li: 要解析的li标签 197 | :param mark_time: 前面已经解析出该条动态的时间点 198 | ''' 199 | if li.xpath('.//span[@data-type="comment_note"]'): 200 | comment_note = {} 201 | comment_note['comment_text'] = self.get_comment_text(li) 202 | comment_note['time'] = mark_time 203 | # comment_note['note_title'] = self.get_obj_title(li) 204 | comment_note['note_id'] = self.get_href_id(li) 205 | print('发表评论', comment_note) 206 | self.timeline['comment_notes'].append(comment_note) 207 | elif li.xpath('.//span[@data-type="like_note"]'): 208 | like_note = {} 209 | like_note['time'] = mark_time 210 | # like_note['note_title'] = self.get_obj_title(li) 211 | like_note['note_id'] = self.get_href_id(li) 212 | print('喜欢文章', like_note) 213 | self.timeline['like_notes'].append(like_note) 214 | elif li.xpath('.//span[@data-type="reward_note"]'): 215 | reward_note = {} 216 | reward_note['time'] = mark_time 217 | # reward_note['note_title'] = self.get_obj_title(li) 218 | reward_note['note_id'] = self.get_href_id(li) 219 | print('赞赏文章', reward_note) 220 | self.timeline['reward_notes'].append(reward_note) 221 | elif li.xpath('.//span[@data-type="share_note"]'): 222 | share_note = {} 223 | share_note['time'] = mark_time 224 | # share_note['note_title'] = self.get_obj_title(li) 225 | share_note['note_id'] = self.get_href_id(li) 226 | print('发表文章', share_note) 227 | self.timeline['share_notes'].append(share_note) 228 | elif li.xpath('.//span[@data-type="like_user"]'): 229 | like_user = {} 230 | like_user['time'] = mark_time 231 | like_user['slug'] = self.get_href_id(li) 232 | print('关注作者', like_user) 233 | self.timeline['like_users'].append(like_user) 234 | elif li.xpath('.//span[@data-type="like_collection"]'): 235 | like_coll = {} 236 | like_coll['time'] = mark_time 237 | like_coll['coll_id'] = self.get_href_id(li) 238 | print('关注专题', like_coll) 239 | self.timeline['like_colls'].append(like_coll) 240 | elif li.xpath('.//span[@data-type="like_comment"]'): 241 | like_comment = {} 242 | like_comment['time'] = mark_time 243 | like_comment['comment_text'] = self.get_comment_text(li) 244 | like_comment['slug'] = self.get_like_comment_slug(li) 245 | like_comment['note_id'] = self.get_like_comment_note_id(li) 246 | print('赞了评论', like_comment) 247 | self.timeline['like_comments'].append(like_comment) 248 | elif li.xpath('.//span[@data-type="like_notebook"]'): 249 | like_notebook = {} 250 | like_notebook['time'] = mark_time 251 | like_notebook['notebook_id'] = self.get_href_id(li) 252 | print('关注文集', like_notebook) 253 | self.timeline['like_notebooks'].append(like_notebook) 254 | elif li.xpath('.//span[@data-type="join_jianshu"]'): 255 | join_time = mark_time 256 | print('加入简书', join_time) 257 | self.timeline['join_time'] = join_time 258 | 259 | def get_mark_time(self, li): 260 | '''获取动态产生的时间''' 261 | mark_time = li.xpath('.//@data-datetime')[0].split('+')[0].replace('T', ' ') 262 | return mark_time 263 | 264 | def get_obj_title(self, li): 265 | '''获取文章标题''' 266 | title = li.xpath('.//a[@class="title"]/text()')[0] 267 | return title 268 | 269 | def get_href_id(self, li): 270 | '''获取文章id''' 271 | href_id = li.xpath('.//a[@class="title"]/@href')[0].split('/')[-1] 272 | return href_id 273 | 274 | def get_comment_text(self, li): 275 | '''获取发表评论的内容''' 276 | like_comment_text = ''.join(li.xpath('.//p[@class="comment"]/text()')) 277 | return like_comment_text 278 | 279 | def get_like_comment_slug(self, li): 280 | '''获取赞了用户评论的slug''' 281 | like_comment_slug = li.xpath('.//div[@class="origin-author single-line"]//@href')[0].split('/')[-1] 282 | return like_comment_slug 283 | 284 | def get_like_comment_note_id(self, li): 285 | '''获取评论文章的id''' 286 | like_comment_note_id = li.xpath('.//div[@class="origin-author single-line"]//@href')[1].split('/')[-1] 287 | return like_comment_note_id 288 | 289 | 290 | class GetAllInfo(): 291 | 292 | # getallinfo() 分别处理首次抓取用户动态和更新用户动态 293 | def getallinfo(self, slug): 294 | if db['user_timeline'].find_one({'slug': slug}): 295 | print('该用户数据已经在数据库中', '\n', '正在更新数据……') 296 | # 更新用户信息 297 | baseinfo = GetUserInfo(slug).get_base_info() 298 | if baseinfo: 299 | self.save_to_mongo(baseinfo, update=True) 300 | print('更新用户信息成功') 301 | timeline = GetUerTimeline(slug, update=True).get_timeline() 302 | if len(timeline) != 8: 303 | # 如果timeline不为空 304 | self.save_update_timeline(slug, timeline) 305 | print('更新用户动态成功') 306 | else: 307 | print('数据库中已是最新动态') 308 | else: 309 | error_info = '404,可能是因为您的链接地址有误、该文章已经被作者删除或转为私密状态。' 310 | self.save_error_txt(slug, error_info) 311 | else: 312 | info = GetUserInfo(slug) 313 | baseinfo = info.get_base_info() 314 | if baseinfo: 315 | timeline = GetUerTimeline(slug).get_timeline() 316 | all_info = dict(baseinfo, **timeline) 317 | # print(all_info) 318 | self.save_to_mongo(all_info) 319 | print('存储用户信息成功') 320 | else: 321 | error_info = '404,可能是因为您的链接地址有误、该文章已经被作者删除或转为私密状态。' 322 | self.save_error_txt(slug, error_info) 323 | 324 | # 存储用户信息 325 | def save_to_mongo(self, all_info, update=False): 326 | if not update: 327 | db['user_timeline'].update({'slug': all_info['slug']}, {'$setOnInsert': all_info}, upsert=True) 328 | else: 329 | db['user_timeline'].update({'slug': all_info['slug']}, {'$set': all_info}, upsert=True) 330 | 331 | # 处理不存在的用户(被封禁等)的错误信息 332 | def save_error_txt(self, slug, error_info): 333 | with open('error.txt', 'a', encoding='utf-8') as f: 334 | f.write('http://www.jianshu.com/u/{0}'.format(slug) + ' ' + error_info + '\n') 335 | f.close() 336 | 337 | # 处理更新动态时的数据库操作(有$push操作,需单独写) 338 | def save_update_timeline(self, slug, timeline): 339 | db['user_timeline'].update({'slug': slug}, {'$set': {'latest_time': timeline['latest_time']}}, upsert=True) 340 | all_time = ['comment_notes', 'like_notes', 'reward_notes', 'share_notes', 341 | 'like_users', 'like_colls', 'like_comments', 'like_notebooks'] 342 | for each_tag in all_time: 343 | if timeline[each_tag]: 344 | db['user_timeline'].update({'slug': slug}, {'$push': {each_tag: {'$each': timeline[each_tag]}}}) 345 | 346 | 347 | if __name__ == '__main__': 348 | getinfo = GetAllInfo() 349 | allinfo = getinfo.getallinfo('5a7aa9b0cf9e') 350 | 351 | ''' 352 | # 多线程抓取(or更新)用户的基本信息及动态。使用时将代码首部的thredpool线程池打开 353 | getinfo = GetAllInfo() 354 | all_mongo_user = [user['slug'] for user in db.user_timeline.find({"followers_num": {'$gte': 100}}, 355 | {'_id': 0, 'slug': 1}, 356 | no_cursor_timeout=True)] 357 | threadpool.map(getinfo.getallinfo, all_mongo_user) 358 | ''' 359 | -------------------------------------------------------------------------------- /flask_jianshu/user_analysis/note_monitor.py: -------------------------------------------------------------------------------- 1 | '''用户发表的文章状态监测:'阅读', '评论', '喜欢', '赞赏', '发布时间']''' 2 | from datetime import datetime 3 | import requests 4 | from lxml import etree 5 | 6 | headers = { "Accept": "text/html, */*; q=0.01", 7 | "Accept-Encoding": "gzip, deflate, sdch", 8 | "Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4", 9 | "Connection": "keep-alive", 10 | "Host": "www.jianshu.com", 11 | "Referer": "http://www.jianshu.com", 12 | "User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36', 13 | "X-Requested-With": "XMLHttpRequest", 14 | "X-PJAX": 'true'} 15 | data = [] 16 | 17 | def get_notes(slug,page=1): 18 | keys = ['阅读', '评论', '喜欢', '赞赏', '发布时间', '采集时间', '文章id', '文章标题'] 19 | # if page==1: 20 | # self.write_to_csv(keys,type='wb') 21 | url='http://www.jianshu.com/u/{slug}?order_by=shared_at&page={page}'.format(slug=slug,page=page) 22 | response = requests.get(url,headers=headers) 23 | tree = etree.HTML(response.text) 24 | all_li = tree.xpath('//ul[@class="note-list"]//li') 25 | if all_li: 26 | for article in all_li: 27 | note_title = article.xpath('.//a[@class="title"]/text()')[0] 28 | note_id = article.xpath('.//a[@class="title"]/@href')[0].split('/')[-1] 29 | push_time = article.xpath('.//span[@class="time"]/@data-shared-at')[0].split('+')[0].replace('T', ' ') 30 | crawl_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 31 | views_num = int(article.xpath('.//i[@class="iconfont ic-list-read"]/following-sibling::text()')[0].strip()) 32 | if article.xpath('.//i[@class="iconfont ic-list-comments"]'): 33 | comments_num = int(article.xpath('.//i[@class="iconfont ic-list-comments"]/following-sibling::text()')[0].strip()) 34 | else: 35 | comments_num = 0 36 | 37 | if article.xpath('.//i[@class="iconfont ic-list-like"]'): 38 | likes_num = int(article.xpath('.//i[@class="iconfont ic-list-like"]/following-sibling::text()')[0].strip()) 39 | else: 40 | likes_num = 0 41 | 42 | if article.xpath('.//i[@class="iconfont ic-list-money"]'): 43 | rewards_num = int(article.xpath('.//i[@class="iconfont ic-list-money"]/following-sibling::text()')[0].strip()) 44 | else: 45 | rewards_num = 0 46 | detail = [note_title,note_id,views_num,comments_num,likes_num,rewards_num,push_time,crawl_time] 47 | print(detail) 48 | data.append(detail) 49 | return get_notes(slug,page+1) 50 | else: 51 | print(data) 52 | return data 53 | 54 | def anlaysis_notes(data): 55 | push_time = reversed([note[-2] for note in data]) 56 | views_num = reversed([note[2] for note in data]) 57 | print(push_time,views_num) 58 | 59 | if __name__ == '__main__': 60 | notes_data = get_notes('yZq3ZV') 61 | anlaysis_notes(notes_data) 62 | 63 | 64 | -------------------------------------------------------------------------------- /scrapy_spider/jianshu_spider/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/malone6/Jianshu/5dba08f03c02643457c5f2428f7c0d8384b53f31/scrapy_spider/jianshu_spider/__init__.py -------------------------------------------------------------------------------- /scrapy_spider/jianshu_spider/items.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Define here the models for your scraped items 4 | # 5 | # See documentation in: 6 | # http://doc.scrapy.org/en/latest/topics/items.html 7 | 8 | import scrapy 9 | 10 | 11 | class JianshuUserBaseInfoItem(scrapy.Item): 12 | # define the fields for your item here like: 13 | # name = scrapy.Field() 14 | nickname = scrapy.Field() 15 | slug = scrapy.Field() 16 | head_pic = scrapy.Field() 17 | gender = scrapy.Field() 18 | is_contract = scrapy.Field() 19 | following_num = scrapy.Field() 20 | followers_num = scrapy.Field() 21 | articles_num = scrapy.Field() 22 | words_num = scrapy.Field() 23 | be_liked_num = scrapy.Field() 24 | pass 25 | 26 | # class JianshuUserTimelineItem(scrapy.Item): 27 | # 28 | # comment_notes = scrapy.Field() 29 | # like_notes = scrapy.Field() 30 | # reward_notes = scrapy.Field() 31 | # share_notes = scrapy.Field() 32 | # like_users = scrapy.Field() 33 | # like_colls = scrapy.Field() 34 | # like_comments = scrapy.Field() 35 | # like_notebooks = scrapy.Field() 36 | # join_time = scrapy.Field() 37 | # pass -------------------------------------------------------------------------------- /scrapy_spider/jianshu_spider/pipelines.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Define your item pipelines here 4 | # 5 | # Don't forget to add your pipeline to the ITEM_PIPELINES setting 6 | # See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html 7 | import pymongo 8 | 9 | class JianshuSpiderPipeline(object): 10 | def process_item(self, item, spider): 11 | return item 12 | 13 | class UserMongoPipeline(object): 14 | collection_name = 'user' 15 | 16 | def __init__(self, mongo_host, mongo_db): 17 | self.mongo_host = mongo_host 18 | self.mongo_db = mongo_db 19 | 20 | @classmethod 21 | def from_crawler(cls, crawler): 22 | return cls( 23 | mongo_host=crawler.settings.get('MONGO_HOST'), 24 | mongo_db=crawler.settings.get('MONGO_DATABASE') 25 | ) 26 | 27 | def open_spider(self, spider): 28 | self.client = pymongo.MongoClient(self.mongo_host) 29 | self.db = self.client[self.mongo_db] 30 | 31 | def close_spider(self, spider): 32 | self.client.close() 33 | 34 | def process_item(self, item, spider): 35 | # 发现则不插入 36 | self.db[self.collection_name].update({'slug': item['slug']}, {'$setOnInsert': item}, upsert=True) 37 | # 插入更新 38 | # self.db[self.collection_name].update({'slug': item['slug']}, item, upsert=True) 39 | return item 40 | -------------------------------------------------------------------------------- /scrapy_spider/jianshu_spider/settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Scrapy settings for jianshu_spider project 4 | # 5 | # For simplicity, this file contains only settings considered important or 6 | # commonly used. You can find more settings consulting the documentation: 7 | # 8 | # http://doc.scrapy.org/en/latest/topics/settings.html 9 | # http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html 10 | # http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html 11 | 12 | BOT_NAME = 'jianshu_spider' 13 | 14 | SPIDER_MODULES = ['jianshu_spider.spiders'] 15 | NEWSPIDER_MODULE = 'jianshu_spider.spiders' 16 | 17 | 18 | # Crawl responsibly by identifying yourself (and your website) on the user-agent 19 | # USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36" 20 | 21 | 22 | # Obey robots.txt rules 23 | ROBOTSTXT_OBEY = False 24 | 25 | # Configure maximum concurrent requests performed by Scrapy (default: 16) 26 | #CONCURRENT_REQUESTS = 32 27 | 28 | # Configure a delay for requests for the same website (default: 0) 29 | # See http://scrapy.readthedocs.org/en/latest/topics/settings.html#download-delay 30 | # See also autothrottle settings and docs 31 | DOWNLOAD_DELAY = 0.5 32 | # The download delay setting will honor only one of: 33 | #CONCURRENT_REQUESTS_PER_DOMAIN = 16 34 | #CONCURRENT_REQUESTS_PER_IP = 16 35 | 36 | # Disable cookies (enabled by default) 37 | #COOKIES_ENABLED = False 38 | 39 | # Disable Telnet Console (enabled by default) 40 | #TELNETCONSOLE_ENABLED = False 41 | 42 | # Override the default request headers: 43 | # DEFAULT_REQUEST_HEADERS = { 44 | # "Accept": "text/html, */*; q=0.01", 45 | # "Accept-Encoding": "gzip, deflate, sdch", 46 | # "Accept-Language": "zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4", 47 | # "Connection": "keep-alive", 48 | # "Host": "www.jianshu.com", 49 | # "Referer": "http://www.jianshu.com", 50 | # "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36", 51 | # "X-PJAX": "true", 52 | # "X-Requested-With": "XMLHttpRequest"} 53 | 54 | # Enable or disable spider middlewares 55 | # See http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html 56 | #SPIDER_MIDDLEWARES = { 57 | # 'jianshu_spider.middlewares.MyCustomSpiderMiddleware': 543, 58 | #} 59 | 60 | # Enable or disable downloader middlewares 61 | # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html 62 | #DOWNLOADER_MIDDLEWARES = { 63 | # 'jianshu_spider.middlewares.MyCustomDownloaderMiddleware': 543, 64 | #} 65 | 66 | # Enable or disable extensions 67 | # See http://scrapy.readthedocs.org/en/latest/topics/extensions.html 68 | #EXTENSIONS = { 69 | # 'scrapy.extensions.telnet.TelnetConsole': None, 70 | #} 71 | 72 | # Configure item pipelines 73 | # See http://scrapy.readthedocs.org/en/latest/topics/item-pipeline.html 74 | ITEM_PIPELINES = { 75 | 'jianshu_spider.pipelines.UserMongoPipeline': 300, 76 | } 77 | 78 | # Enable and configure the AutoThrottle extension (disabled by default) 79 | # See http://doc.scrapy.org/en/latest/topics/autothrottle.html 80 | #AUTOTHROTTLE_ENABLED = True 81 | # The initial download delay 82 | #AUTOTHROTTLE_START_DELAY = 5 83 | # The maximum download delay to be set in case of high latencies 84 | #AUTOTHROTTLE_MAX_DELAY = 60 85 | # The average number of requests Scrapy should be sending in parallel to 86 | # each remote server 87 | #AUTOTHROTTLE_TARGET_CONCURRENCY = 1.0 88 | # Enable showing throttling stats for every response received: 89 | #AUTOTHROTTLE_DEBUG = False 90 | 91 | # Enable and configure HTTP caching (disabled by default) 92 | # See http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html#httpcache-middleware-settings 93 | #HTTPCACHE_ENABLED = True 94 | #HTTPCACHE_EXPIRATION_SECS = 0 95 | #HTTPCACHE_DIR = 'httpcache' 96 | #HTTPCACHE_IGNORE_HTTP_CODES = [] 97 | #HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage' 98 | 99 | 100 | 101 | MONGO_HOST = 'localhost' 102 | MONGO_DATABASE = 'JianShu' -------------------------------------------------------------------------------- /scrapy_spider/jianshu_spider/spiders/__init__.py: -------------------------------------------------------------------------------- 1 | # This package will contain the spiders of your Scrapy project 2 | # 3 | # Please refer to the documentation for information on how to create and manage 4 | # your spiders. 5 | -------------------------------------------------------------------------------- /scrapy_spider/jianshu_spider/spiders/jian_spider.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import re 4 | from fake_useragent import UserAgent 5 | import scrapy 6 | from scrapy import Request 7 | from ..items import JianshuUserBaseInfoItem 8 | 9 | 10 | class JianShuSpider(scrapy.Spider): 11 | name = "jian_spider" 12 | base_headers = {'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4', 13 | 'Host': 'www.jianshu.com', 14 | 'Accept-Encoding': 'gzip, deflate, sdch', 15 | 'X-Requested-With': 'XMLHttpRequest', 16 | 'Accept': 'text/html, */*; q=0.01', 17 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36', 18 | 'Connection': 'keep-alive', 19 | 'Referer': 'http://www.jianshu.com'} 20 | # 只加载列表模块 21 | ajax_headers = dict(base_headers, **{"X-PJAX": "true", 'User-Agent': UserAgent().random}) 22 | 23 | def start_requests(self): 24 | yield Request('http://www.jianshu.com/users/recommended?page=1&per_page=200', 25 | headers=self.base_headers) 26 | 27 | def parse(self, response): 28 | print(response.text) 29 | data = json.loads(response.text) 30 | for user_data in data['users']: 31 | user_slug = user_data['slug'] 32 | yield Request(url='http://www.jianshu.com/u/{slug}'.format(slug=user_slug), 33 | headers=self.base_headers, 34 | callback=self.parse_seeduser, 35 | meta={'slug': user_slug}) 36 | yield Request(url='http://www.jianshu.com/users/{slug}/followers'.format(slug=user_slug), 37 | headers=self.ajax_headers, 38 | callback=self.parse_followers, 39 | meta={'slug': user_slug, 'page': 1}, ) 40 | 41 | def parse_seeduser(self, response): 42 | base_info_item = JianshuUserBaseInfoItem() 43 | slug = response.meta['slug'] 44 | div_main_top = response.xpath('//div[@class="main-top"]') 45 | nickname = div_main_top.xpath('.//div[@class="title"]//a/text()').extract_first() 46 | head_pic = div_main_top.xpath('.//a[@class="avatar"]//img/@src').extract_first() 47 | gender_tmp = div_main_top.xpath('.//div[@class="title"]//i/@class').extract() 48 | if gender_tmp: 49 | gender = gender_tmp[0].split('-')[-1] 50 | else: 51 | gender = 'No' 52 | is_contract_tmp = div_main_top.xpath('.//div[@class="title"]//span[@class="author-tag"]').extract() 53 | if is_contract_tmp: 54 | is_contract = '签约作者' 55 | else: 56 | is_contract = 'No' 57 | info = div_main_top.xpath('.//li//p//text()').extract() 58 | 59 | base_info_item['nickname'] = nickname 60 | base_info_item['slug'] = slug 61 | base_info_item['head_pic'] = head_pic 62 | base_info_item['gender'] = gender 63 | base_info_item['is_contract'] = is_contract 64 | base_info_item['following_num'] = int(info[0]) 65 | base_info_item['followers_num'] = int(info[1]) 66 | base_info_item['articles_num'] = int(info[2]) 67 | base_info_item['words_num'] = int(info[3]) 68 | base_info_item['be_liked_num'] = int(info[4]) 69 | yield base_info_item 70 | 71 | def parse_followers(self, response): 72 | base_info_item = JianshuUserBaseInfoItem() 73 | slug = response.meta['slug'] 74 | page = response.meta['page'] 75 | user_li = response.xpath('//li') 76 | if user_li: 77 | page = page + 1 78 | yield Request(url='http://www.jianshu.com/users/{slug}/followers?page={page}' 79 | .format(slug=slug, page=page), 80 | headers=self.ajax_headers, 81 | callback=self.parse_followers, 82 | meta={'slug': slug, 'page': page}) 83 | for user in user_li: 84 | base_info_item['nickname'] = user.xpath('.//a[@class="name"]/text()').extract_first() 85 | base_info_item['slug'] = user.xpath('.//a[@class="name"]/@href').extract_first().split('/')[-1] 86 | base_info_item['head_pic'] = user.xpath('.//img/@src').extract_first() 87 | span = user.xpath('.//span/text()').extract() 88 | base_info_item['following_num'] = int(re.search('\d+', span[0]).group()) 89 | base_info_item['followers_num'] = int(re.search('\d+', span[1]).group()) 90 | base_info_item['articles_num'] = int(re.search('\d+', span[2]).group()) 91 | meta_text = user.xpath('.//div[@class="meta"]')[1].xpath('text()').extract_first() 92 | meta_num = re.findall('\d+', meta_text) 93 | base_info_item['words_num'] = int(meta_num[0]) 94 | base_info_item['be_liked_num'] = int(meta_num[1]) 95 | 96 | yield base_info_item 97 | else: 98 | pass 99 | 100 | -------------------------------------------------------------------------------- /scrapy_spider/jianshu_spider/spiders/timeline_spider.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import scrapy 3 | 4 | 5 | class TimelineSpiderSpider(scrapy.Spider): 6 | name = "timeline_spider" 7 | start_urls = ( 8 | 'http://www.http://www.jianshu.com/', 9 | ) 10 | 11 | def parse(self, response): 12 | pass 13 | -------------------------------------------------------------------------------- /scrapy_spider/run_spider.py: -------------------------------------------------------------------------------- 1 | from scrapy.cmdline import execute 2 | 3 | execute(['scrapy', 'crawl', 'jian_spider']) -------------------------------------------------------------------------------- /scrapy_spider/scrapy.cfg: -------------------------------------------------------------------------------- 1 | # Automatically created by: scrapy startproject 2 | # 3 | # For more information about the [deploy] section see: 4 | # https://scrapyd.readthedocs.org/en/latest/deploy.html 5 | 6 | [settings] 7 | default = jianshu_spider.settings 8 | 9 | [deploy] 10 | #url = http://localhost:6800/ 11 | project = jianshu_spider 12 | --------------------------------------------------------------------------------