用Class写一个记着用户脱离位置的js插件

媒介

罕见的js插件都很少应用ES6的class,寻常都是经由历程组织函数,而且常常是手写CMDAMD范例来封装一个库,比方如许:

// 援用自:https://www.jianshu.com/p/e65c246beac1
;(function(undefined) {
    "use strict"
    var _global;
    var plugin = {
      // ...
    }
    _global = (function(){ return this || (0, eval)('this'); }());
    if (typeof module !== "undefined" && module.exports) {
        module.exports = plugin;
    } else if (typeof define === "function" && define.amd) {
        define(function(){return plugin;});
    } else {
        !('plugin' in _global) && (_global.plugin = plugin);
    }
}());

但如今都9102年了,是时候祭出我们的ES6大法了,可以用更文雅的的写法来完成一个库,比方如许:

class RememberScroll {
    constructor(options) {
        ...
    }
}
export default RememberScroll

在这篇文章,博主主要经由历程分享近来自身写的一个记着页面转动位置小插件,讲一下怎样用class语法合营webpack 4.xbabel 7.x封装一个可用的库。

项目地点:Github, 在线Demo:Demo

喜好的朋侪愿望能点个Star珍藏一下,异常感谢。

需求泉源

置信许多同砚都邑碰到如许一个需求:用户浏览一个页面并脱离后,再次翻开时须要从新定位到上一次脱离的位置

这个需求很罕见,我们日常平凡在手机上浏览微信民众号的文章页面就有这个功用。想要做到这个需求,也比较好完成,但博主有点懒,心想有无现成的库可以直接用呢?因而去GitHub上搜了一波,发明并没有很好的且相符我需求的,因而得自身完成一下。

为了天真应用(只是部份页面须要这个功用),博主在项目中零丁封装了这个库,本来是在公司项目顶用的,厥后想一想何不开源出来呢?因而有了这个分享,这也是对自身事变的一个总结。

预期结果

博主喜好在做一件事变前先yy一下预期的结果。博主愿望这个库用起来只管简朴,最好是插进去一句代码就可以了,比方如许:

<html>
<head>
  <meta charset="utf-8">
  <title>remember-scroll examples</title>
</head>
<body>
  <div id="content"></div>
  <script src="../dist/remember-scroll.js"></script>
  <script>
    new RememberScroll()
  </script>
</body>
</html>

在想要加上记着用户浏览位置的页面上引入一下库,然后new RememberScroll()初始化一下即可。

下面就带着这个目的,一步一步去完成啦。

设计计划

1. 须要存哪些信息?

用户浏览页面的位置,主要须要存两个字段:哪一个页面脱离时的位置,经由历程这两个字段,我们才可以在用户第二次翻开网站的页面时,掷中该页面,并自动跳转到上一次脱离的位置。

2.存在哪?

记着浏览位置,须要将用户脱离前的浏览位置纪录在客户端的浏览器中。这些信息可以主要存放在:cookiesessionStoragelocalStorage中。

  1. 存放在cookie,大小4K,空间虽有限但也委曲可以。但cookie是每次要求效劳器时都邑携带上的,无形中增添了带宽和效劳器压力,所以团体来说是不太适宜的。
  2. 存放在sessionStorage中,由于仅在当前会话下有用,用户脱离页面sessionStorage就会被消灭,所以不能满足我们的需求。
  3. 存放在localStorage,浏览器可永久保存,大小寻常限定5M,满足我们需求。

综上,末了我们应当挑选localStorage

3. 需注重的题目

  1. 一个站点可以有许多页面,怎样标识是哪一个页面呢?

寻常来说可以用页面的url作为页面的唯一标识,比方:www.xx.com/article/${id},差别的id对应差别的页面。

但博主斟酌到如今许多站点都是用spa了,而且罕见在url后面会带有#xxx的哈希值,如www.xx.com/article/${id}#tag1www.xx.com/article/${id}#tag2这类状况,这可以示意的是同一个页面的差别锚点,所以用url作为页面的唯一标识不太牢靠。

因而,博主决议将这个页面唯一标识作为一个参数来让应用者来决议,权且命名为pageKey,让应用者保证是全站唯一的即可。

  1. 假如用户接见我们的站点中许多许多的页面,由于localStorage是永久保存的,怎样防止localStorage不停积累占用过大?

我们的需求可以仅仅是想近期记着即可,即只须要记着用户的浏览位置几天,可以会更愿望我们存的数据可以自动逾期。

localStorage自身是没有自动逾期机制的,寻常只能在存数据的时候同时存一下时候戳,然后在应用时推断是不是逾期。假如只能是在应用时才推断是不是消灭,而新接见页面时又会天生新的纪录,localStorage中一向都邑存在最少一条纪录的,也就是说没法真正完成自动逾期。这里不禁就认为有点过剩了,既然都是会一向保存纪录在localStorage中,那痛快就不推断了,咱换一个思绪:只纪录有限的最新页面数目

举个例子:

我们网站有个文章页:
www.xx.com/articles/${id},每一个的
id示意差别的文章,我们只纪录用户最新接见的5篇文章,即保护一个长度为5的行列。

比方当前网站有id从1100篇文章,用户离别接见第1,2,3,4,5篇文章时,这5篇文章都邑纪录脱离的位置,而当用户翻开第六篇文章时,第六条纪录入队的同时第一条纪录出队,此时localStorage中纪录的是2,3,4,5,6这几篇文章的位置,这就保证了localStorage永久不会积累存储数据且旧纪录会跟着不停接见新页面自动“逾期”。

为了更天真一点,博主决议给这个插件增添一个maxLength的参数,示意当前站点下纪录的最新的页面最大数目,默许值设为5,假如有小伙伴的需求是纪录更多的页面,可以经由历程这个参数来设置。

4. 完成思绪

  1. 我们须要时候监听用户浏览页面时的转动条的位置,可以经由历程window.onscroll事宜,取得当前的转动条位置:scrollTop
  2. scrollTop和页面唯一标识pageKey存进localStorage中。
  3. 用户再次翻开之前接见过的页面,在页面初始化时,读取localStorage中的数据,推断页面的pageKey是不是一致,若一致则将页面的转动条位置自动转动到响应的scrollTop值。

是不是是很简朴?不过完成的历程当中须要注重一下细节,比方做一下防抖处置惩罚。

完成步骤

逼逼了这么久,是时候最先撸代码了。

1.封装localStorage东西要领

工欲善其事,必先利其器。为更好效劳接下来的事变,我们先简朴封装一下挪用localStorage的几个要领,重如果get,set,remove

// storage.js
const Storage = {
  isSupport () {
    if (window.localStorage) {
      return true
    } else {
      console.error('Your browser cannot support localStorage!')
      return false
    }
  },
  get (key) {
    if (!this.isSupport) {
      return
    }
    const data = window.localStorage.getItem(key)
    return data ? JSON.parse(data) : undefined
  },
  remove (key) {
    if (!this.isSupport) {
      return
    }
    window.localStorage.removeItem(key)
  },
  set (key, data) {
    if (!this.isSupport) {
      return
    }
    const newData = JSON.stringify(data)
    window.localStorage.setItem(key, newData)
  }
}

export default Storage

2. class大法

class即类,本质上虽然是一个function,但应用class定义一个类会更直观。我们为行将写的库起个名字为RememberScroll,最先就是以下的模样啦:

import Storage from './storage'
class RememberScroll {
    constructor() {
        
    }
}

1.处置惩罚传进来的参数

我们须要在类的组织函数constructor中吸收参数,并掩盖默许参数。

还记得上面我们预期的用法吗?即new RememberScroll({pageKey: 'myPage', maxLength: 10})

  constructor (options) {
    let defaultOptions = {
      pageKey: '_page1', // 当前页面的唯一标识
      maxLength: 5
    }
    this.options = Object.assign({}, defaultOptions, options)
}

假如没有传参数,就会应用默许的参数,假如传了参数,就应用传进来的参数。this.options就是终究处置惩罚后的参数啦。

2.页面初始化

当页面初始化时,我们须要做三件事变:

  • loaclStorage掏出缓存列表
  • 将转动条转动到纪录的位置(如有纪录的话);
  • 注册window.onscroll事宜监听用户转动行动;

因而,须要在组织函数中就实行initScrolladdScrollEvent这两个要领:

import Storage from './utils/storage'
class RememberScroll {
  constructor (options) {
    // ...
    this.storageKey = '_rememberScroll'
    this.list = Storage.get(this.storageKey) || []
    this.initScroll()
    this.addScrollEvent()
  }
  initScroll () {
    // ...
  }
  addScrollEvent () {
    // ...
  }
}

这里我们将localStorage中的键名命名为_rememberScroll,应当可以只管防止和寻常站点应用localStorage的键名争执。

3.监听转动事宜:addScrollEvent()的完成

  addScrollEvent () {
    window.onscroll = () => {
      // 猎取最新的位置,只纪录垂直方向的位置
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      // 组织当前页面的数据对象
      const data = {
        pageKey: this.options.pageKey,
        y: scrollTop
      }
      let index = this.list.findIndex(item => item.pageKey === data.pageKey)
      if (index >= 0) {
        // 之前缓存过该页面,则替换掉之前的纪录
        this.list.splice(index, 1, data)
      } else {
        // 假如已超越长度了,则消灭一条最早的纪录
        if (this.list.length >= this.options.maxLength) {
          this.list.shift()
        }
        this.list.push(data)
      }
      // 更新localStorage内里的纪录
      Storage.set(this.storageKey, this.list)
    }
  }

ps:这里最好须要做一下防抖处置惩罚

4.初始化转动条位置: initScroll()的完成

initScroll () {
    // 先推断是不是有纪录
    if (this.list.length) {
      // 当前页面pageKey是不是一致
      let currentPage = this.list.find(item => item.pageKey === this.options.pageKey)
      if (currentPage) {
        setTimeout(() => {
          // 一致,则转动到对应的y值
          window.scrollTo(0, currentPage.y)
        }, 0)
    }
}

仔细的同砚可以会发明,这里用了setTimeout,而不是直接挪用window.scrollTo。这是由于博主在这里碰到坑了,这里涉及到页面加载实行递次的题目。

在实行window.scrollTo前,页面必需是已加载完成了的,转动条要已存在才可以转动对吧。假如页面加载时直接实行,当时的scroll高度可认为0,window.scrollTo实行就会无效。假如页面的数据是异步猎取的,也会致使window.scrollTo无效。因而用setTimeout会是比较稳的一个方法。

5.将模块export出去

末了我们须要将模块export出去,团体代码大概是这个模样:

import Storage from './utils/storage'

class RememberScroll {
  constructor (options) {
    let defaultOptions = {
      pageKey: '_page1', // 当前页面的唯一标识
      maxLength: 5
    }
    this.storageKey = '_rememberScroll'
    // 参数
    this.options = Object.assign({}, defaultOptions, options)

    // 缓存列表
    this.list = Storage.get(this.storageKey) || []
    this.initScroll()
    this.addScrollEvent()
  }
  initScroll () {
    // ...
  }
  addScrollEvent () {
    // ...
  }
}

export default RememberScroll

如许就基本完成全部插件的功用啦,是不是是很简朴哈哈。篇幅缘由就不贴细致代码了,可以直接到GitHub上看:remember-scroll

打包

接下来应当是本文的重点了,首先要清晰为何要打包?

  1. 将项目中所用到的js文件兼并,只对外输出一个js文件。
  2. 使项目同时支撑AMD,CMD、浏览器<script>标签引入,即umd范例。
  3. 合营babel,将es6语法转为es5语法,兼容低版本浏览器。

PS: 由于webpack和babel更新速率很快,网上许多教程可以早已过期,如今(2019-03)的版本已是babel 7.3.0,webpack 4.29.6, 本篇文章只分享如今的最新的设置要领,因而本篇文章也是会过期的,读者们请注重版本号。

npm init项目

我们先新建一个目次,这里名为:remember-scroll,然后将上面写好的remember-scroll.js放进remember-scroll/src/目次下。

PS:寻常项目的资本文件都放在src目次下,为了显得专业点,最好将
remember-scroll.js改名为
index.js。)

此时项目还没有package.json文件,因而在根目次实行敕令初始化package.json:

npm init

须要依据提醒填写一些项目相干信息。

装置webpack和webpack-cli

运转webpack敕令时须要同时装上webpack-cli

npm i webpack webpack-cli -D

设置webpack.config.js

在根目次中增添一个webpack.config.js,根据webpack官网的示例代码设置:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'remember-scroll.js' // 修正下输出的称号
  }
};

然后在package.json的script中设置运转webpack的敕令:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack --mode=development --colors"
  },

如许设置完成,在根目次运转npm run dev,会自动天生dist/remember-scroll.js

此时已完成了我们的第一个小目的:赚它一个亿,哦不,是将storage.jsindex.js兼并输出为一个remember-scroll.js

这类简朴的打包可以称为:
非模块化打包。由于我们在js文件中没有经由历程
AMD的return或许
CommonJS的exports或许this导出模块自身,致使模块被引入的时候只能实行代码而没法将模块引入后赋值给别的模块应用。

支撑umd范例

置信许多同砚都听过AMD,CommonJS范例了,不清晰的同砚可以看看阮一峰先生的引见:Javascript模块化编程(二):AMD范例

为了让我们的插件同时支撑AMD,CommonJS,所以须要将我们的插件打包为umd通用模块。

之前看过一篇文章:怎样定义一个高逼格的原生JS插件,在没有应用webpack打包时,须要在插件中手写支撑这些模块化的代码:

// 援用自:https://www.jianshu.com/p/e65c246beac1
;(function(undefined) {
    "use strict"
    var _global;
    var plugin = {
      // ...
    }
    // 末了将插件对象暴露给全局对象
    _global = (function(){ return this || (0, eval)('this'); }());
    if (typeof module !== "undefined" && module.exports) {
        module.exports = plugin;
    } else if (typeof define === "function" && define.amd) {
        define(function(){return plugin;});
    } else {
        !('plugin' in _global) && (_global.plugin = plugin);
    }
}());

博主看到这坨东西,也是有点晕,不能不信服大佬就是大佬。还好如今有了webpack,我们如今只须要写好主体症结代码,webpack会帮我们处置惩罚好这些打包的题目。

在webpack4中,我们可以将js打包为一个库的情势,概况可看:[Webpack Expose the Library
](https://webpack.js.org/guides…。在我们这里只需在output中加上library属性:

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'remember-scroll.js',
    library: 'RememberScroll',
    libraryTarget: 'umd',
    libraryExport: 'default'
  }
};

注重libraryTargetumd,就是我们要打包的目的范例为umd

当我们在html中经由历程script标签引入这个js时,会在window下注册RememberScroll这个变量(相似引入jQuery时会在全局注册$这个变量)。此时就直接应用RememberScroll这个变量了。

<script src="../dist/remember-scroll.js"></script>
<script>
  console.log(RememberScroll)
</script>

这里有个坑须要注重一下,假如没有加上libraryExport: 'default',由于我们代码中是export default RememberScroll,打包出来的代码会相似:

{
    'default': {
        initScroll () {}
    }
}

而我们希冀的是如许:

{
    initScroll () {}
}

即我们愿望的是直接输出default中的内容,而不是隔着一层default。所以这里还要加上libraryExport: 'default',打包时只输出default的内容。

PS: webpack英文文档看得有点懵逼,这个坑让博主折腾了良久才爬起来,所以特别讲下。刚兴致的同砚可以看下文档:
output.libraryExport

到这里,已完成了我们的第二个小目的:支撑umd范例

应用babel-loader

上面我们打包出来的js,实在已可以一般运转在支撑es6语法的浏览器中了,比方chrome。但想要运转在IE10,IE11中,还得让神器Babel帮我们一把。

PS: 虽然许多人说不斟酌兼容IE了,但作为一个通用性的库,骨董级的IE7,8,9可以不兼容,但较新版本的IE10,11照样须要兼容一下的。

Babel是一个JavaScript转译器,置信人人都听过。由于JavaScript在不停的生长,然则浏览器的生长速率跟不上,新的语法和特征不能立时被浏览器支撑,因而须要一个能将新语法新特征转为当代浏览器能明白的语法的转译器,而Babel就是充当了转译器的角色。

PS:之前博主一向认为(置信许多刚打仗Babel的同砚也是如许),只需应用了Babel,就可以宁神无痛应用ES6的语法了,但是事变并非如许。
Babel编译并不会做polyfill,Babel为了保证准确的语义,只能转换语法而不会增添或修正原有的属性和要领。要想无痛应用ES6,还须要合营polyfill。不太明白的同砚,在这里引荐人人看下这篇文章:
21 分钟通晓前端 Polyfill 计划,写得异常通俗易懂。

总的来说,就是Babel须要合营polyfill来应用。

Babel更新比较频仍,网上搜出来的许多设置教程是旧版本的,可以并不实用最新的Babel 7.x,所以我们这里折腾一下最新的webpack4设置Babel计划:babel-loader
1.装置babel-loader,@babel/core@babel/preset-env

npm install -D babel-loader @babel/core @babel/preset-env core-js

core-js是JavaScript模块化规范库,在
@babel/preset-env按需打包时会应用
core-js中的函数,因而这里也是要装置的,不然打包的时候会报错。

2.修正webpack.config.js设置,增添rules

const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'remember-scroll.js',
    library: 'RememberScroll',
    libraryTarget: 'umd',
    libraryExport: 'default'
  },
  module: {
    rules: [
        {
          test: /\.m?js$/,
          exclude: /(node_modules|bower_components)/,
          use: {
            loader: 'babel-loader'
          }
        }
      ]
  }
};

示意.js的代码应用babel-loader打包。

3.在根目次新建babel.config.js,参考Babel官网

const presets = [
  [
    "@babel/env",
    {
      targets: {
        browsers: [
            "last 1 version",
            "> 1%",
            "maintained node versions",
            "not dead"
          ]
      },
      useBuiltIns: "usage",
    },
  ],
];

browsers设置的是目的浏览器,即我们想要兼容到哪些浏览器,比方我们想兼容到IE10,就可以写上IE10,然后webpack会在打包时自动为我们的库增添polyfill兼容到IE10。

博主这里用的是引荐的参数,来自:npm browserslist,如许就可以兼容到大多数浏览器啦。

设置好后,npm run dev打包即可。
此时,我们已完成了第三个小目的:兼容低版本浏览器。

临盆环境打包

npm run dev打包出来的js会比较大,寻常还须要紧缩一下,而我们可以应用webpack的production形式,就会自动为我们紧缩js,输出一个临盆环境可用的包。在package.json再增添一条build敕令:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack --mode=production -o dist/remember-scroll.min.js --colors",
    "dev": "webpack --mode=development --colors"
  },

这里同时指定了输出的文件名为:remember-scroll.min.js,寻常临盆环境就是应用这个文件啦。

宣布到npm

经由上面的步骤,我们已写完这个库,有需求的同砚可以将库宣布到npm,让更多的人可以轻易用到你这个库。

在宣布到npm前,须要修正一下package.json,完美下形貌作者之类的信息,最主要的是要增添main进口文件:

{
    "main": "dist/remember-scroll.min.js",
}

如许他人应用你的库时,可以直接经由历程import RememberScroll from 'remember-scroll'来应用remember-scroll.min.js

宣布步骤:

  1. 先到https://www.npmjs.com/注册一个账号,然后考证邮箱。
  2. 然后在敕令行中输入:npm adduser,输入账号密码邮箱登录。
  3. 运转npm publish上传包,几分钟后就可以在npm搜到你的包了。

至此,基本就完成一个插件的开辟宣布历程啦。

不过一个优异的开源项目,还应当要有细致的申明文档,应用示例等等,人人可以参考下博主这个项目的README.md中文README.md

末了

文章写了好几天了,可谓煞费苦心,虽然比较烦琐,但应当比较清晰地交卸了怎样应用ES6语法从零写一个记着用户脱离位置的js插件,也很细致地解说了怎样用最新的webpack打包我们的库,愿望能让人人都有所收成,也愿望人人能到GitHub上点个Star勉励一下啦。

remember-scroll这个插件实在几个月前就已宣布到npm了,一向比较忙(懒)没写章分享。虽然功用简朴但很有诚意,能兼容到IE9。

应用起来也异常轻易简朴,可直接经由历程script标签cdn引入,也可以在vue中import RememberScroll from 'remember-scroll'应用。文档中有细致的应用示例:

项目地点Github,在线Demo

迎接人人批评交换,也迎接PR,同时愿望人人能点个Star勉励一下啦。

    原文作者:fengxianqi
    原文地址: https://segmentfault.com/a/1190000018569836
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞