day-094-ninety-four-20230619-图片缩略图幻灯片-插件封装的步骤-NativeApp与WebApp
图片缩略图幻灯片
总体思路
-
整理思路。
- 所有的结构都包在一个盒子中。盒子里有两层内容:
- 盒子宽高由前端根据设计稿来定。
- 盒子宽高应具体到px,以便内部使用百分比进行布局。
- 一层是封面,用于展示播放时长和视频主图。
- 一层是进度图,用于展示进度条对应的视频缩略图。根据用户鼠标在盒子中横向距离与盒子宽度的比例,控制进度图的进度,之后进度图控制精灵图中显示的区域。
- 精灵图是一张组图,组图由多张小图组成,精灵图每次完整地对应一张小图。
- 所有的结构都包在一个盒子中。盒子里有两层内容:
-
先搭结构。
<!-- 每一项。 --> <div class="slide-box"> <!-- 封面图 --> <div class="cover"> <img src="./images/fengmian.jpg" alt="封面图" /> <span class="time">03:48</span> </div> <!-- 进度层 --> <div class="progress"> <div class="bar"> <div class="all"> <div class="already"></div> </div> </div> </div> </div>
-
根据DOM结构来写样式。
-
使用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:48</span>
</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>
插件封装
-
js插件封装的技巧
- 有一类插件,只需要导入js,然后基于特定的方法执行,可以快速创建出所需要的结构、样式、功能。
- 这类插件,在其内部:
- 会动态创建HTML结构。
- 基于行内样式,把需要的样式都编写好。
- 实现出对应的功能和逻辑。
- 存在的一些问题:
- 在插件内部,HTML/CSS/js代码都是混合在一起的,不方便维护。
- 不方便使用者去修改元素的样式以及结构。
- 基于上只能去修改插件的源码。
- …
- 此类插件适用于:结构和样式很少,或者没有结构或样式,以及后续几乎不会修改结构和样式的情况!
- 比如说:
- 回到顶部插件。
- 局部滚动的插件。
- IScroll.js。
- …
- 比如说:
- 这类插件,在其内部:
- 大部分插件,都需要开发者按照插件的要求,编写出必须的基本结构和样式。样式可以导入插件提供的基础样式,然后再导入相应的js代码,执行特定的方法,实现出具体的功能!
- 例如:
- Swiper.js。
- 这类插件是主流插件。
- 这类插件,需要开发者使用的时候,按照要求去构建结构、样式等,所以我们需要编写一个详细的使用说明文档!
- 例如:
- 有一类插件,只需要导入js,然后基于特定的方法执行,可以快速创建出所需要的结构、样式、功能。
-
在封装插件的时候,我们一般都基于面向对象的方式来处理。
-
在相同的页面中,我们的插件可能会执行很多次。为了保证每一次调用插件,相互之间不影响,我们采用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:48</span> </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 = `<div class="zfslide-all"> <div class="zfslide-already"></div> </div>`; 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; } })();
-
-
在html中使用插件。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>测试幻灯片插件</title> <link rel="stylesheet" href="./css/image.slide.css" /> <!-- <link rel="stylesheet" href="./css/zfslide.plugin.min.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:48</span> </div> <div class="zfslide-progress"></div> </div> <!-- 需进度条-但不实际写进度条的DOM骨架、需要封面时间的幻灯片。 --> <div class="zfslide-box" id="slide3"> <div class="zfslide-cover"> <img src="./images/fengmian.jpg" alt="封面图" /> <span class="time">03:48</span> </div> <div class="zfslide-progress"></div> </div> </body> </html> <script src="./js/zfslide.plugin.js"></script> <!-- <script src="./js/zfslide.plugin.min.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>
插件封装的步骤
- 对完整功能的进行实现。
- 整理出那些需要进行改变。
- 用面向对象进行封装。
- 对传入的数据进行校验。
- 对功能改成面向对象的方式。
- 对css文件要进行压缩。
- 在线JS/CSS/HTML压缩(采用YUI Compressor实现)
- 对js文件要进行压缩。
- 要把ES6代码转成ES5代码,之后再对ES5代码进行压缩。
- 把ES6代码转成ES5代码。
- Babel压缩参数配置
- ,对ES5代码进行压缩。
- 在线JS/CSS/HTML压缩(采用YUI Compressor实现)
- 使用时,使用的压缩后的css代码与js代码。
NativeApp与WebApp
NativeApp与WebApp的发展
-
NativeApp与WebApp
- NativeApp:原生的App。
- 技术栈:安卓开发(java-native)和IOS开发(object-c/swift)。
- 和前端没多大关系。
- 相关特点:
- 直接安装和运行在手机操作系统中的。
- 应用商店 --> 下载 --> 安装。
- 优势:
- 性能强。
- 操作体验好。
- 功能强大。
- 可以直接调用手机各种软硬件,前提需要用户同意。
- 支持离线缓存…
- 弊端:
- 不能跨平台。
- 需要招聘两个开发团队,开发两套产品。
- 开发的app需要上传到应用商店。
- 有审核。
- 很多内容需要用户自主更新才可以看到!
- …
- 不能跨平台。
- 直接安装和运行在手机操作系统中的。
- 技术栈:安卓开发(java-native)和IOS开发(object-c/swift)。
- WebApp:H5页面。
- 技术栈:前端开发相关的技术栈。
- 相关特点:
- 无需安装,直接在手机端的浏览器或webview中运行。
- 浏览器 --> 输入网址/或扫码 --> 预览页面。
- 优势:
- 可以跨平台。
- 只需要开发一套产品,安卓/IOS中的浏览器基本上都是webkit内核。
- 无需上传应用商店。
- 不需要审核。
- 用户看到的永远都是最新的。
- …
- 可以跨平台。
- 弊端:
- 性能和操作体验都差一些。
- 只不过随着技术发展,H5的操作体验也起来越好了。
- 无法直接调用手机的软硬件。
- 如需调用这些功能,需要宿主环境的支持。
- 离线缓存效果差。
- …
- 性能和操作体验都差一些。
- 无需安装,直接在手机端的浏览器或webview中运行。
- 所以当代移动端App的开发,是把NativeApp和WebApp混合在一起来使用的!
- 我们把这种方式称之为:Hybrid混合App开发!
- NativeApp:原生的App。
-
随着科技的发展,H5占据一款App的比例越来越高,Native比例在快速的降低,直到有的App,Native只需要搭建一个壳子,内部全部都是H5来写的…
-
后来出现了一些前端框架,可以帮助我们快速构建出一个App应用的壳子。
- 也就是把我们写的H5套一层App的壳子。
- PhoneGap。
- Cordova。
- …
-
再后来经过逐步的完善,出现了移动端App开发的前端框架:
- 主要框架类型:
- RN(ReactNative),基于React语法。
- uni-app,基于Vue语法。
- flutter,基于dart语法。
- …
- 它们属于:基于js编写代码,最后框架会把我们写的代码,编译为IOS和安卓的代码,实现真正的NativeApp!
- 主要框架类型:
-
目前开发App已经是过去式了。开发出来,也不容易推广。目前主流的模式是小程序!
- 小程序全部是前端的活。和IOS/安卓没有半毛钱关系。
- 小程序的原理:按照平台既定的语法和提供的组件,去实现小程序的相关页面、样式、功能,在小程序内部也可以调用平台提供的方法。最后把开发完毕的小程序,部署到指定的平台。
- 语法和平时前端开发类型,但是也有一些区别。
- 在小程序内部也可以调用平台提供的方法…
- 目前有一些前端框架,只需要我们写一套代码,就可以生成多套小程序。
- uni-app,Vue语法。
- taro,React语法。
- …
Hybrid混合App开发
- App本身的壳子由NativeApp来处理。
- 实现手机软硬件的调取。
- 一些追求极致体验的效果,也由NativeApp来完成。
- NativeApp有一个webview框架,类似于浏览器的webkit内核。
- …
- 目前,一些需要常规展示和操作的功能,基本上都交给H5开发。
- 把H5页面嵌入到NativeApp的webview框架中。
- 我们只需要把部署后的H5页面地址给原生开发,他们会帮着把H5嵌入到webview框架中。
- …
- 把H5页面嵌入到NativeApp的webview框架中。
- H5和NativeApp的通信问题。
-
方案一:jsBridge-主流模式,适用于IOS和安卓。
- 编写一个BridgeJS文件,文件中实现了调用NativeApp的方法。
- 在H5页面中,只需要导入这个文件,这样在window全局对象上,就拥有了调用NativeApp的方法,按照要求直接调用即可!
- 核心:BridgeJS文件。
- 有些这个是前端来写,有些是安卓和IOS已经帮着实现了。
- 例如:在微信App(NativeApp)中渲染H5页面,我们需要在H5中,调用微信App的一些功能(比如分享、支付…)。
-
在H5页面中,导入微信写好的jsBridge文件。
- JS-SDK说明文档
- 微信写好的jsBridge文件
- 导入后,在window中多了一个wx的对象,在此对象中包含了微信App给我们提供的方法。
-
基于wx.config(…)执行一些初始的配置。
wx.config({ debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId: '', // 必填,公众号的唯一标识 timestamp: , // 必填,生成签名的时间戳 nonceStr: '', // 必填,生成签名的随机串 signature: '',// 必填,签名 jsApiList: [] // 必填,需要使用的JS接口列表 });
-
基于wx对象上的方法,调用微信提供的api。
wx.ready(function () { //需在用户可能点击分享按钮前就先调用 wx.updateAppMessageShareData({ title: '', // 分享标题 desc: '', // 分享描述 link: '', // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致 imgUrl: '', // 分享图标 success: function () { // 设置成功 } }) });
-
-
方案二:URL劫持-适用于IOS。文章来源:https://www.toymoban.com/news/detail-497724.html
-
原理:因为H5是运行在NativeApp中的,所以H5中的任何操作,原生App都可以知道,例如页面跳转。文章来源地址https://www.toymoban.com/news/detail-497724.html
location.href=`wx://xxx.cn/xxx` //其中:`wx://`是伪协议,是我们和原生商量好的,只要我跳转这样的地址,原生就进行拦截! //基于拦截,原生就知道了我们要请求的功能和传递的参数,然后帮我们调用其所实现的某个方法即可!
-
-
进阶参考
到了这里,关于20230619----重返学习-图片缩略图幻灯片-插件封装的步骤-NativeApp与WebApp的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!