12.其它细节

上面11篇论述了主要的原理,作为最后一篇,我们主要论述单页面相比于多页面的灵活的部分,如何使用最原始的html,js,css发挥web的最大魅力。

动画,过场动画

单页面要比多页面灵活,拥有过场动画是它最直观的表现,并且页面切换不会出现白屏的现象。

在底层ReplaceProto对象中,专门设置了两个dom,一个dom作为放置当前页面,另一个dom放置切换页面。在切换过程中,通过两个dom的过渡产生过场动画。动画方式在css3中定义,然后根据情况进行不同的动画切换, 同时完成后退和前进两个过场

  1. 进场动画,当切换到另一个页面。另一个页面就会以入场动画显示到屏幕上;
  2. 出场动画,当切换到另一个页面,当前页面就会以出场动画退出屏幕中。

它们共同组成了页面的动画效果

默认的动画由App对象里面定义,在附带的app.css中定义行为

this.options = {
    changeClass: "app-change",
    backClass: "app-back",
    area: "change-state",
    in: { // 进场动画
        back: "page-out",
        change: "page-in"
    },
    out: { // 出场动画
        back: "page-in-reverse",
        change: "page-out-reverse"
    }
};

在切换页面的时候,也可以通过传入不同的类名,实现自定义动画, 详细见ReplaceProto的render方法。render: function (pagename, isReplace, option)
如果使用replaceState方法不会触发动画。
其中第三个参数不仅可以传数据,也可以传动画配置,并且在_getReplaceClass方法中进行切换类名,达到动画的效果。

 _getReplaceClass: function (option) {
    var options = this.options;
    option = option || {};
    return {
        backStaticClass: option.backClass || options.backClass,
        changeStaticClass: option.changeClass || options.changeClass,
        areaClass: option.area || options.area,
        backActiveClass: this.isRenderBack ? 
            option.backClass || options.out.back : 
            option.backClass || options.in.back,
        changeActiveClass: this.isRenderBack ? 
            option.changeClass || options.out.change : 
            option.changeClass || options.in.change
    }
},

类似的在PopUp对象中同样有页面切换动画,PopUp对象还要有个弹出弹窗和关闭的动画。请看动画设置:

this.options = {
    className: "popup",
    changeClass: "popup-change",
    backClass: "popup-back",
    area: "popup-state",
    currentIn: { // 显示页面上的入场
        backClass: "popup-active-out",
        changeClass: "popup-active-in"
    },
    currentOut: { // 显示页面上的出场
        backClass: "popup-active-in-reverse",
        changeClass: "popup-active-out-reverse"
    },
    staticIn: "popup-static-out", // 弹窗进入页面
    staticOut: "popup-static-in" // 弹窗隐藏
};

可以在PopUp配置或在show和hidden方法中设置不同的动画效果。

show: function (dom, config, target, isDismisBeforeShow) // config.staticIn

hidden: function (option, bk) // option.staticOut或show方法传入的config.staticOut

对于组件的页面切换动画和App的动画切换是一致的。

初始化页面历史缓存

如果用户从首页进入网站,我们不用对history记录做任何更改,这是一种常规情况。然而网站的入口是url,如果url不是进入首页,而是从详情页或是付款页进入网站,或者通过其它手段(扫码等)。

当用户在该页面进行了操作(如果不做任何操作,点击后退应该是退出网站),为了让用户有一个浏览流程,在详情页点击后退应该是返回到列表页。

原理很简单,判断进入初始页,然后先pushState若干个页面。然后渲染页面。从App.initialize的方法来看,有一个_prevAttachHistory(prevHistory)操作,这就是为了该目的。这里的prevHistory是由开发初始化定义的App实例对象的getInitHistory来得到,这是一个url数组。 例如我们初始化定义

app.getInitHistory = function (pathname) {
    if (pathname === "/detail") return [ "/home" ]; // 也可以带参数的url
}

通过这样配置后,页面直接打开详情页,在详情页操作后,点击后退键回退到首页,而不是退出该网站。注意,如果进入页面后没有任何操作,直接点击退出,是不会退到首页的,这是浏览器的一大特性,只有用户与该页面有交互,即使是touchstart,mousedown都能后退到首页。后退到前页面,因为历史记录中存的不是页面缓存,它是初始化一个新的Page对象,会走完Page的整个生命周期流程

加速加载优化,service延迟加载

从上面篇章提到,整个资源获取都是按需加载的,即使组件中的小图标的svg片段也是如此。特别一个大的首页,里面包含着很多小图标,引入很多组件js,片段html,需要等它们全部加载完会耗费很长时间。因此我们可以对常见的资源进行统一定义加载。

  1. 可以将多个组件的js放在一个js文件中,因为获取组件的时候,判断是否存在已经加载过该组件, 需要手动关闭。在App.define, app.defineComponent, app.definePage, app.definePopup的第三个参数设为true,其中app为App的实例对象。

  2. 可以将引入的html当作字符串, 注入到Http.cache对象中 App.setHttpCache(url, str); 由于转换为字符串编入js中(以字符串形式编辑html比较困难,不建议全部放入js中)。

  3. 如果某个service服务仅仅与该方法有关,把引入代码从顶部移到方法内,。比如

     clickHandler: function (ev) {
         App.require(["mapTool"], function (mapTool) {
             // dosomething
         })
     }
    

持续化数据和异步操作

对于Component,Page, PopUp,App对象中,都定义一个data,这是一个放置临时数据的对象。特别是Page,这个data格式有严格的要求,必须是可序列化的。

在Page执行restore的时候,我们是无法获取到在其他页面改变了的全局数据,我们可以把数据存放在App实例对象的data里面,然后切换页面的时候获取App对象中的data数据,进行有效的局部刷新操作。这要比重新去后端获取一次更合理。

如果在PopUp对象的Page对象,也可以用相同的方式放在PopUp的对象的data里面。统一放置方法可以用this.parent.data.key = value;

一个网站,很少使用大量的持久化数据,对于webapp,使用持久化数据却很常见。我们可以使用同步操作的localStorage或异步操作的IndexedDB数据库。在使用IndexedDB的时候要特别的注意,异步操作时页面突然切换导致回调函数执行错误,因此尽量在执行完后再执行跳转。如果不可避免,可以仿照Ajax的封装方式,跳转页面的时候让获取数据操作停止,存数据的回调中判断是否当前页面是有效显示页。
对于不做中断的异步操作,可以放在staticPage中执行,因为它是一直存在的,等执行完毕后通过触发app.currentPage的自定义事件,进行相关的更新操作。

浏览器缓存

浏览器缓存可以提高页面的加载速度,有时候却成了我们更新项目的一大阻碍,特别在测试公众号的时候。因此我们通过后缀名版本来解决问题。比如我们在index.html上header的新建script上加一句App.version = 2.0; 再把str.js和index.js版本号更改相同的版本号。接着框架内部会把所有的引入的js以及获取的静态文件都会加上?v=2.0重置所有的版本号

内存使用

单页面对于内存的使用非常的苛刻。如果无限制的使用,会导致页面奔溃或让手机设备快速耗电。因此这里我们对每个模块的引用都做了严格的处理。对于dom和事件,在页面销毁的时候都会自动去销毁。而且引用外库的时候,我们建议在init初始化数据,在dispose方法中进行数据清理。对于引用没有数据回收操作的外库的时候要特别小心,不能无限制的新建对象,这样会导致页面堆积越多的内存而无法销毁。我们可以使用创建一个对象,然后进行无限制的使用(单例模式)。

通过异步按需加载的好处在于,能让内存使用量尽可能的变少。在加载首页的时候,我们的网页的内存使用量基本和纯使用静态页面的网站持平的。随着组件量以及页面的增加,我们缓存了大量的js,静态html,会让内存使用量增多,而且缓存在history的Page对象,也会提高内存使用量。尽管如此,我们的内存使用量也不会超过静态页面太多,在可以接受的范围之内。

本地文件打开

有时候我们需要本地直接打开,虽然用的很少,但还是会遇到的。比如原生App嵌入webview,在没有网的情况下要打开网站,这时候只能通过打开本地页面,虽然功能有点阉割,但是页面布局还是可以复用原来的,我们需要做一下的调整:

  1. 把绝对引用全部改为相对引用,这一点都是可以支持的,通过改写App.join方法统一更改js的加入;
  2. 无法使用按需加载(可以按需加载js),需要对所有的静态资源进行统一加入。这一点难度虽然不大,但是操作起来比较繁琐;
  3. 如果使用了strui框架,需要针对使用的组件进行资源统一加入。

虽然付出了一些努力,但是非常值得的,底层是支持本地文件打开的,以下功能会受到限制:

  1. 无法使用ajax功能,无法与后端进行交互;
  2. 无法使用history api。在本地打开,会将这些方法全部过滤掉。

支持SSR

SSR对于单页面相对多页面是一个缺陷,尽管努力去弥补,但总是无法尽善尽美。而且单纯在前端努力是无法完成的。这里我们通过以下手段来实现SSR:

  1. 如果是纯粹单页面,index.html的body元素应该只有引用script的。我们在body上加入data-preload属性,代表它使用了SSR, 然后加入{{{body}}}, 代表着服务生成的html代码;

  2. 接着在服务端,复制前端的renderHTML方法。根据浏览器访问地址,拼装填充{{{body}}}的html片段(这里后端使用nodejs,可以共享前端的js方法);

  3. index.js中的声明App对象的时候,currentName需要根据pathname改变。如下代码

    // location.pathname = "/detail";
    if (location.pathname == "/detail") app = new App("hello str", "static", "detail");
    else app = new App("hello str", "static", "home");
    

虽然通过上面拼接成的html可以在浏览器上直接打开,然而浏览器毕竟没有直接渲染组件的功能, 因此渲染的结果不会太好。只能让搜索引擎获取到, 然后通过下面的方法进行分别渲染:

if (preload === "true") {
    // 通过更改html,渲染组件
    var activeHtml = pageContainer.innerHTML;
    pageContainer.innerHTML = "";
    var staticHtml = document.body.innerHTML;
    if (staticPage.preload) staticPage.preload();
    staticPage.initialize(body, staticHtml, {}, function () {
        body.removeAttribute("data-preload");
        that._initCurrentPage(staticPage, currentPage, prevHistory);
        if (currentPage.preload) currentPage.preload();
        currentPage.initialize(that.changeDom, activeHtml);
    });
}
else {
    // 常规手段
    staticPage.render(function (html) {
        staticPage.initialize(body, html, {}, function () {
            that._initCurrentPage(staticPage, currentPage, prevHistory)
            currentPage.render(function (html) {
                currentPage.initialize(that.changeDom, html);
            });
        });
    });
}

超越web,支持electron等方式

现在web在通过electron打包成桌面App,因为electron使用了node技术,所以在获取文件或者资源的时候就不一样了。我们可以更改fetch方法:

if (typeof __dirname === "string") {
    require("fs").readFile(url, "utf-8", function (error, result) {
        if (error) console.log(error);
        else Http.cache.dispatch(url, result);
    })
}
else {
    var obj = createRequest(this, url, undefined, function (result) {
        Http.cache.dispatch(url, result);
    }, {
        onabort: function (ev) {
            Http.cache.remove(url);
        }
    });
    this.http.ajax(obj);
}

更改获取文件路径方法

App.join = function (url) {
    if (typeof __dirname === "string") return require("path").join(__dirname, url);
    return url;
};

还需要更改Page获取资源路径方法

function getBaseUrl(urlStr) {
    if (typeof __dirname === "string") {
        urlStr = require("path").join(__dirname, urlStr);
        return urlStr.split("\\").slice(0, -1).join("\\") + "\\";
    }
    return urlStr.split("/").slice(0, -1).join("/") + "/";
}

这样子,就可以兼容electron的环境了。

使用pwa技术

有幸于web的发展进程都是围绕了渐进增强的路线,所以很容易让webapp支持pwa的各种技术

  1. 在index.html中加入以下js字段

     if ("serviceWorker" in navigator) {
         navigator.serviceWorker.register("./sw.js")
             .then(function (registration) {
                 console.log("ServiceWorker registration successful with scope: ", 
                     registration.scope);
             }).catch(function (err) {
                 console.log("ServiceWorker registration failed: ", err);
             });
     }
    
  2. 然后在根目录中加入sw.js,里面的内容自定义,代码略。

总结

这一篇作为完结篇,主要对常见的开发问题进行了进一步的扩展。

推广

底层框架开源地址:https://gitee.com/string-for-100w/string
演示网站: https://www.renxuan.tech/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EOS是一个操作系统,而fat12.c是与FAT12文件系统相关的代码文件。FAT12是一种用于磁盘存储的文件系统格式,被广泛应用于早期的计算机系统中。fat12.c代码实现了FAT12文件系统的相关功能,包括文件的创建、打开、读写和删除等操作。 该代码文件主要包含以下功能: 1. 初始化FAT12文件系统:该代码会读取磁盘引导扇区,获取文件系统的相关信息,并将这些信息加载到内存中。 2. 文件的创建和打开:该代码提供了函数来创建新的文件或打开已有的文件。通过读取文件目录表和文件分配表,该代码可以定位到文件在磁盘中的位置,并在内存中建立相应的文件控制块。 3. 文件的读写:该代码提供了函数来读取和写入文件的内容。通过文件控制块中的信息,该代码可以判断文件的当前读写位置,并根据需要在磁盘上定位到相应的位置进行读写操作。 4. 文件的删除:该代码提供了函数来删除文件。删除文件时,该代码会将文件的目录表项和文件分配表相应位置的标志位置为"空闲",表示该文件已被删除。 以上是该代码文件的主要功能概述,实际上,FAT12文件系统还有许多其他操作,如目录的创建和删除、文件大小的修改等,这些功能在代码文件中也有相应的实现。通过阅读和理解代码,可以深入了解FAT12文件系统的原理和实现细节,为更好地理解和使用操作系统提供了帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值