20230619----重返学习-图片缩略图幻灯片-插件封装的步骤-NativeApp与WebApp

day-094-ninety-four-20230619-图片缩略图幻灯片-插件封装的步骤-NativeApp与WebApp

图片缩略图幻灯片

总体思路

  1. 整理思路。

    • 所有的结构都包在一个盒子中。盒子里有两层内容:
      1. 盒子宽高由前端根据设计稿来定。
      2. 盒子宽高应具体到px,以便内部使用百分比进行布局。
      • 一层是封面,用于展示播放时长和视频主图。
      • 一层是进度图,用于展示进度条对应的视频缩略图。根据用户鼠标在盒子中横向距离与盒子宽度的比例,控制进度图的进度,之后进度图控制精灵图中显示的区域。
        1. 精灵图是一张组图,组图由多张小图组成,精灵图每次完整地对应一张小图。
  2. 先搭结构。

    
    <div class="slide-box">
      
      <div class="cover">
        <img src="./images/fengmian.jpg" alt="封面图" />
        <span class="time">03:48span>
      div>
      
      <div class="progress">
        <div class="bar">
          <div class="all">
            <div class="already">div>
          div>
        div>
      div>
    div>
    
  3. 根据DOM结构来写样式。

  4. 使用js代码来写幻灯片的js逻辑代码。

常见的命名方式

  • 常见的命名方式:
    • kabab-case -> 连接符 slide-box
    • camelCase -> 小驼峰 slideBox
    • PascalCase -> 帕斯卡尔(大驼峰) SlideBox

幻灯片进度条

初版源码-未进行封装

DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>幻灯片title>
    <link rel="stylesheet" href="./css/reset.min.css" />
    <style>
      /* 禁止页面出现横向滚动条。 */
      html,
      body {
        overflow-x: hidden;
      }

      /* 开始设置盒子 */
      .slide-box {
        position: relative;
        box-sizing: border-box;
        margin: 20px auto;
        width: 200px;
        height: 100px;
        overflow: hidden;
      }

      /* 封面区域样式 */
      .slide-box .cover {
        position: relative;
        height: 100%;
        background: #eee;
        transition: opacity 0.3s;
      }
      .slide-box .cover img {
        display: block;
        height: 100%;
        width: 100%;
      }
      .slide-box .cover .time {
        position: absolute;
        right: 5px;
        bottom: 5px;
        z-index: 1;
        padding: 5px 10px;
        font-size: 12px;
        color: #fff;
        background-color: rgba(0, 0, 0, 0.6);
      }

      /* 进度区域样式 */
      .slide-box .progress {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: #eee;
        opacity: 0;
        z-index: -1;
        transition: opacity 0.3s;
      }
      .slide-box:hover .progress {
        opacity: 1;
        z-index: 10;
      }
      .slide-box:hover .cover {
        opacity: 0;
      }
      /* .slide-box .progress {
        background: url("./images/225865760.png") no-repeat;

        //真实开发中,背景图大小是不固定的。
          //宽度:盒子宽 * 10 ; //10是协定好的,一般一行展示10个。不过也可以由服务器传递一个数字过来。
          //高度:Math.ceil(总图片数 / 10) * 盒子高 ; //总图片数是后端返回的。
        background-size: 2000px 550px;
        
        background-position: 0 0;//默认展示背景图中的第一张,但是后期需要根据鼠标在盒子中的位置,计算出应该展示那一张,并计算出对应的background-position值。
      } */
      .slide-box .progress .bar {
        position: absolute;
        top: 0;
        left: 0;
        box-sizing: border-box;
        padding: 5px 10px;
        width: 100%;
        background: #000;
      }
      .slide-box .progress .bar .all {
        position: relative;
        height: 4px;
        border-radius: 2px;
        background: rgba(255, 255, 255, 0.3);
        overflow: hidden;
      }
      .slide-box .progress .bar .already {
        position: absolute;
        top: 0;
        bottom: 0;
        height: 100%;
        border-radius: 2px;
        background: #fff;

        width: 0%; /* 播放进度,默认为0,后期根据鼠标的移动,动态计算进度。 */
      }
    style>
  head>
  <body>
    
    <div class="slide-box">
      
      <div class="cover">
        <img src="./images/fengmian.jpg" alt="封面图" />
        <span class="time">03:48span>
      div>
      
      <div class="progress">
        <div class="bar">
          <div class="all">
            <div class="already">div>
          div>
        div>
      div>
    div>
  body>
html>
<script>
  // 获取需要操作的元素。
  const slideBox = document.querySelector(".slide-box");
  const progressBox = slideBox.querySelector(".progress");
  const alreadyBox = progressBox.querySelector(".already");

  // 初始化数据和样式。
  let W = slideBox.offsetWidth; //盒子宽度。
  let H = slideBox.offsetHeight; //盒子高度。
  let C = 10; //进度条图片中每一行展示的数量。
  let T = 46; //进度条图片的总图片数量。
  let URL = "./images/225865760.png"; //背景图地址,从服务器获取的是绝对地址。
  progressBox.style.cssText = `
    background: url(${URL}) no-repeat;
    background-size: ${W * C}px ${Math.ceil(T / C) * H}px;
    background-position: 0 0;
  `;
  alreadyBox.style.width = "0%";

  //移动计算。
  const computed = function computed(ev) {
    // 前提是没有横向滚动条。
    console.log(`slideBox-->`, slideBox);

    let l = ev.clientX - slideBox.getBoundingClientRect().left; //鼠标距离盒子左侧的距离。
    let ratio = l / W; //计算鼠标距离盒子左边的百分比。
    ratio = ratio < 0 ? 0 : ratio > 1 ? 1 : ratio;
    // 控制进度条的样式。
    alreadyBox.style.width = `${ratio * 100}%`;
    // 控制背景图的样式。

    let N = Math.round(T * ratio);
    // console.log(`N-->`, N);
    N = N < 1 ? 1 : N > T ? T : N;//解决盒子开头移动时出现空白的情况。
    let x = N % C; //计算在第几列。
    x = x === 0 ? C : x;//解决在盒子中间移动时出现空白的情况。
    let y = Math.ceil(N / C); //计算在第几行。
    progressBox.style.backgroundPosition = `${-(x - 1) * W}px ${
      -(y - 1) * H
    }px`;
  };
  slideBox.addEventListener("mouseenter", computed); //初次进来盒子。
  slideBox.addEventListener("mousemove", computed); //在盒子内移动。
script>

插件封装

  1. js插件封装的技巧

    1. 有一类插件,只需要导入js,然后基于特定的方法执行,可以快速创建出所需要的结构、样式、功能。
      • 这类插件,在其内部:
        • 会动态创建HTML结构。
        • 基于行内样式,把需要的样式都编写好。
        • 实现出对应的功能和逻辑。
      • 存在的一些问题:
        • 在插件内部,HTML/CSS/js代码都是混合在一起的,不方便维护。
        • 不方便使用者去修改元素的样式以及结构。
          • 基于上只能去修改插件的源码。
      • 此类插件适用于:结构和样式很少,或者没有结构或样式,以及后续几乎不会修改结构和样式的情况!
        • 比如说:
          • 回到顶部插件。
          • 局部滚动的插件。
            • IScroll.js。
    2. 大部分插件,都需要开发者按照插件的要求,编写出必须的基本结构和样式。样式可以导入插件提供的基础样式,然后再导入相应的js代码,执行特定的方法,实现出具体的功能!
      • 例如:
        • Swiper.js。
      • 这类插件是主流插件。
      • 这类插件,需要开发者使用的时候,按照要求去构建结构、样式等,所以我们需要编写一个详细的使用说明文档!
  2. 在封装插件的时候,我们一般都基于面向对象的方式来处理。

    • 在相同的页面中,我们的插件可能会执行很多次。为了保证每一次调用插件,相互之间不影响,我们采用OOP面向对象模式。

    • 插件本身是一个类,每一次使用都是创建这个类的一个实例,实例和实例之间是不影响的!对于一些通用的处理方法,实例和实例之间还可以共用!

    • 对插件样式的处理:

      DOCTYPE html>
      <html>
        <body>
          
          <div class="zfslide-box" id="slide1">
            <div class="zfslide-cover">
              <img src="./images/fengmian.jpg" alt="封面图" />
            div>
            <div class="zfslide-progress">
              <div class="zfslide-bar">
                <div class="zfslide-all">
                  <div class="zfslide-already">div>
                div>
              div>
            div>
          div>
      
          
          <div class="zfslide-box" id="slide2">
            <div class="zfslide-cover">
              <img src="./images/fengmian.jpg" alt="封面图" />
              <span class="time">03:48span>
            div>
            <div class="zfslide-progress">div>
          div>
        body>
      html>
      
      <style>
        /* 在插件样式的基础上,自己增加/调整新的样式。 */
        #slide1,
        #slide2 {
          margin: 20px auto;
        }
        #slide2 {
          width: 300px;
          height: 165px;
        }
      style>
      
      <style>
        /* 对新增的元素自己设置样式 */
        #slide2 .time {
          position: absolute;
          right: 5px;
          bottom: 5px;
          z-index: 1;
          padding: 5px 10px;
          font-size: 12px;
          color: #fff;
          background-color: rgba(0, 0, 0, 0.6);
        }
      style>
      
      <style>
        /* 修改插件内部元素的样式。 */
        .zfslide-progress .zfslide-all{
          background-color: rgba(255, 255, 255, 0.7);
        }
      style>
      
    • 对插件逻辑的处理:

      (function () {
        // 检测是否为纯粹对象
        const toString = Object.prototype.toString;
        const isPlainObject = function isPlainObject(obj) {
          // 先校验:如果基于 toString/call ,检测结果都不是 [object Object],则一定不是纯粹对象
          if (toString.call(obj) !== "[object Object]") return false;
          let proto = Object.getPrototypeOf(obj);
          if (!proto) return true;
          let Ctor = "constructor" in obj && obj.constructor;
          return Ctor === Object;
        };
      
        class Slide {
          constructor(container, { back, total, column, progress }) {
            //获取需要的元素。
            this.container = container;
            this.progress = container.querySelector(".zfslide-progress");
            if (!this.progress) {
              throw new TypeError(`缺少幻灯片图层-样式式: zfslide-progress`);
            }
            // 动态创建进度条。
            if (progress&&!container.querySelector(".zfslide-already")) {
              let div = document.createElement("div");
              div.className = "zfslide-bar";
              div.innerHTML = `
      `
      ; this.progress.appendChild(div) } this.already = container.querySelector(".zfslide-already"); // 初始化样式和数据 this.W = container.offsetWidth; //盒子宽度。 this.H = container.offsetHeight; //盒子高度。 this.column = +column; //进度条图片中每一行展示的数量。 this.total = +total; //进度条图片的总图片数量。 this.back = back; //背景图地址,从服务器获取的是绝对地址。 this.init(); // 事件绑定。-需要在绑定先,把computed中的this绑定为当前实例。 container.addEventListener("mouseenter", this.computed.bind(this)); //初次进来盒子。 container.addEventListener("mousemove", this.computed.bind(this)); //在盒子内移动。 } // 处理化样式。 init() { let { progress, already, back, W, column, total, H } = this; if (already) { already.style.width = "0%"; } progress.style.cssText = ` background: url(${back}) no-repeat; background-size: ${W * column}px ${Math.ceil(total / column) * H}px; background-position: 0 0; `; } // 鼠标在盒子中移动的计算。 computed(ev) { let { container, already, progress, W, H, total, column } = this; // 前提是没有横向滚动条。 console.log(`container-->`, container); // 计算鼠标距离盒子左边的百分比。 let l = ev.clientX - container.getBoundingClientRect().left; //鼠标距离盒子左侧的距离。 let ratio = l / W; //计算鼠标距离盒子左边的百分比。 ratio = ratio < 0 ? 0 : ratio > 1 ? 1 : ratio; // 控制进度条的样式。 if (already) { already.style.width = `${ratio * 100}%`; } // 计算出当前应该展示那一张。 let N = Math.round(total * ratio); //当前要显示的第几张。 // console.log(`N-->`, N); N = N < 1 ? 1 : N > total ? total : N; //解决盒子开头移动时出现空白的情况。 // 计算出当前这一张图片在背景图中是第x列和第y行。 let y = Math.ceil(N / column); //计算在第几行。 let x = N % column; //计算在第几列。 x = x === 0 ? column : x; //解决在盒子中间移动时出现空白的情况。 //控制背景图的位置。 progress.style.backgroundPosition = `${-(x - 1) * W}px ${-(y - 1) * H}px`; } } // 暴露API。 const zfslide = function zfslide(selector, options) { //如果传递的是一个选择器:我们基于选择器获取相应的元素。 if (typeof selector === "string") { selector = document.querySelector(selector); } // 确保容器的正确性 if (!selector || selector?.nodeType !== 1) { throw new TypeError(`指定的容器不存在`); } //处理配置项。 if (!isPlainObject(options)) { options = {}; } options = Object.assign( { back: "", //背景图图片地址。 total: 0, //背景图片图片总数。 column: 10, //背景图每行数量有多少个。 progress: true, }, options ); // 确保背景图图片地址和背景图每行数量是存在的。 if (!options.back || options.total === 0) { throw new TypeError(`请先指定幻灯片背景图和背景图单行数量`); } return new Slide(selector, options); }; if (typeof window !== "undefined") { window["zfslide"] = zfslide; } if (typeof module === "object" && typeof module.exports === "object") { module.exports = zfslide; } })();
  3. 在html中使用插件。

    DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8" />
        <title>测试幻灯片插件title>
        <link rel="stylesheet" href="./css/image.slide.css" />
        
        <link rel="stylesheet" href="./css/reset.min.css" />
        <style>
          /* 在插件样式的基础上,自己增加/调整新的样式。 */
          #slide1,
          #slide2 {
            margin: 20px auto;
          }
          #slide2 {
            width: 300px;
            height: 165px;
          }
    
          /* 对新增的元素自己设置样式 */
          #slide2 .time {
            position: absolute;
            right: 5px;
            bottom: 5px;
            z-index: 1;
            padding: 5px 10px;
            font-size: 12px;
            color: #fff;
            background-color: rgba(0, 0, 0, 0.6);
          }
    
          /* 修改插件内部元素的样式。 */
          .zfslide-progress .zfslide-all {
            background-color: rgba(255, 255, 0, 0.4);
          }
        style>
      head>
      <body>
        
        <div class="zfslide-box" id="slide1">
          <div class="zfslide-cover">
            <img src="./images/fengmian.jpg" alt="封面图" />
          div>
          <div class="zfslide-progress">
            <div class="zfslide-bar">
              <div class="zfslide-all">
                <div class="zfslide-already">div>
              div>
            div>
          div>
        div>
    
        
        <div class="zfslide-box" id="slide2">
          <div class="zfslide-cover">
            <img src="./images/fengmian.jpg" alt="封面图" />
            <span class="time">03:48span>
          div>
          <div class="zfslide-progress">div>
        div>
    
        
        <div class="zfslide-box" id="slide3">
          <div class="zfslide-cover">
            <img src="./images/fengmian.jpg" alt="封面图" />
            <span class="time">03:48span>
          div>
          <div class="zfslide-progress">div>
        div>
      body>
    html>
    <script src="./js/zfslide.plugin.js">script>
    
    <script>
      zfslide("#slide1", {
        back: "./images/225865760.png",
        total: 46,
      });
      zfslide("#slide2", {
        back: "./images/3x3.jpg",
        total: 9,
        column: 3,
        progress: false,
      });
      zfslide("#slide3", {
        back: "./images/3x3.jpg",
        total: 9,
        column: 3,
      });
    script>
    

插件封装的步骤

  1. 对完整功能的进行实现。
  2. 整理出那些需要进行改变。
  3. 用面向对象进行封装。
  4. 对传入的数据进行校验。
  5. 对功能改成面向对象的方式。
  6. 对css文件要进行压缩。
    • 在线JS/CSS/HTML压缩(采用YUI Compressor实现)
  7. 对js文件要进行压缩。
    • 要把ES6代码转成ES5代码,之后再对ES5代码进行压缩。
    1. 把ES6代码转成ES5代码。
      • Babel压缩参数配置
    2. ,对ES5代码进行压缩。
      • 在线JS/CSS/HTML压缩(采用YUI Compressor实现)
  8. 使用时,使用的压缩后的css代码与js代码。

NativeApp与WebApp

NativeApp与WebApp的发展

  • NativeApp与WebApp

    • NativeApp:原生的App。
      • 技术栈:安卓开发(java-native)和IOS开发(object-c/swift)。
        • 和前端没多大关系。
      • 相关特点:
        • 直接安装和运行在手机操作系统中的。
          • 应用商店 --> 下载 --> 安装。
        • 优势:
          • 性能强。
          • 操作体验好。
          • 功能强大。
            • 可以直接调用手机各种软硬件,前提需要用户同意。
          • 支持离线缓存…
        • 弊端:
          • 不能跨平台。
            • 需要招聘两个开发团队,开发两套产品。
          • 开发的app需要上传到应用商店。
            • 有审核。
          • 很多内容需要用户自主更新才可以看到!
    • WebApp:H5页面。
      • 技术栈:前端开发相关的技术栈。
      • 相关特点:
        • 无需安装,直接在手机端的浏览器或webview中运行。
          • 浏览器 --> 输入网址/或扫码 --> 预览页面。
        • 优势:
          • 可以跨平台。
            • 只需要开发一套产品,安卓/IOS中的浏览器基本上都是webkit内核。
          • 无需上传应用商店。
            • 不需要审核。
          • 用户看到的永远都是最新的。
        • 弊端:
          • 性能和操作体验都差一些。
            • 只不过随着技术发展,H5的操作体验也起来越好了。
          • 无法直接调用手机的软硬件。
            • 如需调用这些功能,需要宿主环境的支持。
          • 离线缓存效果差。
    • 所以当代移动端App的开发,是把NativeApp和WebApp混合在一起来使用的!
      • 我们把这种方式称之为:Hybrid混合App开发!
  • 随着科技的发展,H5占据一款App的比例越来越高,Native比例在快速的降低,直到有的App,Native只需要搭建一个壳子,内部全部都是H5来写的…

  • 后来出现了一些前端框架,可以帮助我们快速构建出一个App应用的壳子。

    1. 也就是把我们写的H5套一层App的壳子。
    • PhoneGap。
    • Cordova。
  • 再后来经过逐步的完善,出现了移动端App开发的前端框架:

    • 主要框架类型:
      • RN(ReactNative),基于React语法。
      • uni-app,基于Vue语法。
      • flutter,基于dart语法。
    • 它们属于:基于js编写代码,最后框架会把我们写的代码,编译为IOS和安卓的代码,实现真正的NativeApp!
  • 目前开发App已经是过去式了。开发出来,也不容易推广。目前主流的模式是小程序!

    • 小程序全部是前端的活。和IOS/安卓没有半毛钱关系。
    • 小程序的原理:按照平台既定的语法和提供的组件,去实现小程序的相关页面、样式、功能,在小程序内部也可以调用平台提供的方法。最后把开发完毕的小程序,部署到指定的平台。
      1. 语法和平时前端开发类型,但是也有一些区别。
      2. 在小程序内部也可以调用平台提供的方法…
      • 目前有一些前端框架,只需要我们写一套代码,就可以生成多套小程序。
        • uni-app,Vue语法。
        • taro,React语法。

Hybrid混合App开发

  1. App本身的壳子由NativeApp来处理。
    • 实现手机软硬件的调取。
    • 一些追求极致体验的效果,也由NativeApp来完成。
    • NativeApp有一个webview框架,类似于浏览器的webkit内核。
  2. 目前,一些需要常规展示和操作的功能,基本上都交给H5开发。
    • 把H5页面嵌入到NativeApp的webview框架中。
      • 我们只需要把部署后的H5页面地址给原生开发,他们会帮着把H5嵌入到webview框架中。
  • H5和NativeApp的通信问题。
    • 方案一:jsBridge-主流模式,适用于IOS和安卓。

      1. 编写一个BridgeJS文件,文件中实现了调用NativeApp的方法。
      2. 在H5页面中,只需要导入这个文件,这样在window全局对象上,就拥有了调用NativeApp的方法,按照要求直接调用即可!
      • 核心:BridgeJS文件。
        • 有些这个是前端来写,有些是安卓和IOS已经帮着实现了。
      • 例如:在微信App(NativeApp)中渲染H5页面,我们需要在H5中,调用微信App的一些功能(比如分享、支付…)。
        1. 在H5页面中,导入微信写好的jsBridge文件。

          • JS-SDK说明文档
          • 微信写好的jsBridge文件
          • 导入后,在window中多了一个wx的对象,在此对象中包含了微信App给我们提供的方法。
        2. 基于wx.config(…)执行一些初始的配置。

          wx.config({
            debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
            appId: '', // 必填,公众号的唯一标识
            timestamp: , // 必填,生成签名的时间戳
            nonceStr: '', // 必填,生成签名的随机串
            signature: '',// 必填,签名
            jsApiList: [] // 必填,需要使用的JS接口列表
          });
          
        3. 基于wx对象上的方法,调用微信提供的api。

          wx.ready(function () {   //需在用户可能点击分享按钮前就先调用
            wx.updateAppMessageShareData({ 
              title: '', // 分享标题
              desc: '', // 分享描述
              link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
              imgUrl: '', // 分享图标
              success: function () {
                // 设置成功
              }
            })
          });
          
    • 方案二:URL劫持-适用于IOS。

      • 原理:因为H5是运行在NativeApp中的,所以H5中的任何操作,原生App都可以知道,例如页面跳转。

        location.href=`wx://xxx.cn/xxx`
        //其中:`wx://`是伪协议,是我们和原生商量好的,只要我跳转这样的地址,原生就进行拦截!
        //基于拦截,原生就知道了我们要请求的功能和传递的参数,然后帮我们调用其所实现的某个方法即可!
        

进阶参考

你可能感兴趣的:(重返学习,ES6,原生js学习,学习,web,app)