其他前端问题

目录

1、协议

2、游览器页面跳转过程

3、跨域问题

4、vue.js如何编译temple,获取其他引擎何如编译(art-Template)

5、link与@important区别

6、同步与异步区别

7、cookie,sessionStorage,localStorage的区别

8、闭包

9、数组的方法

10、字符串包含判断

12、数组的去重


 

1、协议

Http(HyperText Transfer Protocol) 超文本传输协议:用于分布式、协作式和超媒体信息系统的应用层协议

客户端向服务器发送一个请求报文,请求报文包含请求的方法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版本、成功或者错误代码、服务器信息、响应头部和响应数据。


2、游览器页面跳转过程

1)、URL地址的解析:一个URL包括协议,网络地址,资源路径

2)、DNS域名解析:解析域名找到IP,通过这个IP可以找到客户端到服务器端的唯一路径.

3)、浏览器主机根据ip地址与服务器建立TCP连接(三次握手)

图文理解:

客户端:“你好,你的快递到了,人在家吗?”

服务端:“在的,送来就行。”

客户端:“好嘞。”

4)、发送HTTP请求

请求行包括请求方法、URI、HTTP版本。首部字段传递重要信息,包括请求首部字段、通用首部字段和实体首部字段。

5)、服务器处理请求

在响应结果中都会有个一个HTTP状态码

6)、断开TCP连接(4次握手)

  • 客户端发起中断请求,发送FIN到服务端
  • 服务端收到请求,可能数据还没有发完。这个时候不会关闭socket,而是回复ACK,告诉客户端知道了
  • 客户端进入Fin_Wait状态,继续等待服务端端的FIN报文。服务端端发送完毕后,会向客户端发送FIN
  • 客户端客服端收到后就回复ACK,并关闭连接
  • 客户端:“兄弟,我这边没数据要传了,咱关闭连接吧。”
  • 服务端:“收到,我看看我这边有木有数据了。”
  • 服务端:“兄弟,我这边也没数据要传你了,咱可以关闭连接了。”
  • 客户端:“好嘞。”

7)、浏览器解析文件

浏览器通过解析HTML,生成DOM树,解析CSS,生成CSS规则树,然后通过DOM树和CSS规则树生成渲染树。渲染树与DOM树不同,渲染树中并没有head、display为none等不必显示的节点。

要注意的是,浏览器的解析过程并非是串连进行的,比如在解析CSS的同时,可以继续加载解析HTML,但在解析执行JS脚本时,会停止解析后续HTML,这就会出现阻塞问题,关于JS阻塞相关问题,(js阻塞问题可以自行百度,或者等我有时间更新一篇文章,单独解释阻塞)


作者:掘金吐槽摸鱼一号
链接:https://juejin.cn/post/6844903919395536910
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

8)、浏览器布局渲染

根据渲染树布局,计算CSS样式,即每个节点在页面中的大小和位置等几何信息。HTML默认是流式布局的,CSS和js会打破这种布局,改变DOM的外观样式以及大小和位置。这时就要提到两个重要概念:repaint(重绘)和reflow(回流)。

repaint:屏幕的一部分重画,不影响整体布局,比如某个CSS的背景色变了,但元素的几何尺寸和位置不变。

reflow: 意味着元件的几何尺寸变了,我们需要重新验证并计算渲染树。是渲染树的一部分或全部发生了变化。这就是Reflow,或是Layout。

此处解答参考,具体如下详情
作者:掘金吐槽摸鱼一号
链接:https://juejin.cn/post/6844903919395536910
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3、跨域问题

4、vue.js如何编译temple,获取其他引擎何如编译(art-Template)

5、link与@important区别

本质上,这两种方式都是为了加载css文件,但还是存在细微的差别

差别1:老祖宗的差别,link属于XHTML标签,而@import完全是css提供的一种方式。

    link标签除了可以加载css外,还可以做很多其他的事情,比如定义RSS,定义rel连接属性等,@import只能加载CSS。

差别2:加载顺序的差别:当一个页面被夹在的时候(就是被浏览者浏览的时候),link引用的CSS会同时被加载,而@import引用的CSS会等到页面全部被下载完再加载。所以有时候浏览@import加载CSS的页面               时会没有样式(就是闪烁),网速慢的时候还挺明显。

差别3:兼容性的差别。由于@import是CSS2.1提出的所以老的浏览器不支持,@import只有在IE5以上的才能识别,而link标签无此问题,完全兼容。

差别4:使用dom控制样式时的差别。当时用JavaScript控制dom去改变样式的时候,只能使用link标签,因为@import不是dom可以控制的(不支持)。

6、同步与异步区别

1)同步:

同步,我的理解是一种线性执行的方式,执行的流程不能跨越。一般用于流程性比较强的程序,我们做的用户登录功能也是同步处理的,必须用户通过用户名和密码验证后才能进入系统的操作。

2)异步:

异步,是一种并行处理的方式,不必等待一个程序执行完,可以执行其它的任务。在程序中异步处理的结果通常使用回调函数来处理结果。

//ajax
ajax({
    
    url:''.
    method:'',//get/post
    data:'',//参数
//true请求均为异步请求。false同步请求
    async:boolean,
    contentType:'',
    beforeSend:function,//请求钱的操作
    dataType:'',
    success:function,
    error:function,
    complete:function,//完成回掉函数
    timeout:number(ms)
})
//Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
let service = axios.create({
  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认是 get

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
  transformRequest: [function (data) {
    // 对 data 进行任意转换处理

    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对 data 进行任意转换处理

    return data;
  }],

  // `headers` 是即将被发送的自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是即将与请求一起发送的 URL 参数
  // 必须是一个无格式对象(plain object)或 URLSearchParams 对象
  params: {
    ID: 12345
  },

  // `paramsSerializer` 是一个负责 `params` 序列化的函数
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function(params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求主体被发送的数据
  // 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
  // 在没有设置 `transformRequest` 时,必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属:FormData, File, Blob
  // - Node 专属: Stream
  data: {
    firstName: 'Fred'
  },

  // `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
  // 如果请求话费了超过 `timeout` 的时间,请求将被中断
  timeout: 1000,

  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // 默认的

  // `adapter` 允许自定义处理请求,以使测试更轻松
  // 返回一个 promise 并应用一个有效的响应 (查阅 [response docs](#response-api)).
  adapter: function (config) {
    /* ... */
  },

  // `auth` 表示应该使用 HTTP 基础验证,并提供凭据
  // 这将设置一个 `Authorization` 头,覆写掉现有的任意使用 `headers` 设置的自定义 `Authorization`头
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // `responseType` 表示服务器响应的数据类型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
  responseType: 'json', // 默认的

  // `xsrfCookieName` 是用作 xsrf token 的值的cookie的名称
  xsrfCookieName: 'XSRF-TOKEN', // default

  // `xsrfHeaderName` 是承载 xsrf token 的值的 HTTP 头的名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认的

  // `onUploadProgress` 允许为上传处理进度事件
  onUploadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  onDownloadProgress: function (progressEvent) {
    // 对原生进度事件的处理
  },

  // `maxContentLength` 定义允许的响应内容的最大尺寸
  maxContentLength: 2000,

  // `validateStatus` 定义对于给定的HTTP 响应状态码是 resolve 或 reject  promise 。如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),promise 将被 resolve; 否则,promise 将被 rejecte
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认的
  },

  // `maxRedirects` 定义在 node.js 中 follow 的最大重定向数目
  // 如果设置为0,将不会 follow 任何重定向
  maxRedirects: 5, // 默认的

  // `httpAgent` 和 `httpsAgent` 分别在 node.js 中用于定义在执行 http 和 https 时使用的自定义代理。允许像这样配置选项:
  // `keepAlive` 默认没有启用
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // 'proxy' 定义代理服务器的主机名称和端口
  // `auth` 表示 HTTP 基础验证应当用于连接代理,并提供凭据
  // 这将会设置一个 `Proxy-Authorization` 头,覆写掉已有的通过使用 `header` 设置的自定义 `Proxy-Authorization` 头。
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: : {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // `cancelToken` 指定用于取消请求的 cancel token
  // (查看后面的 Cancellation 这节了解更多)
  cancelToken: new CancelToken(function (cancel) {
  })
});
// 在实例已创建后修改默认值
service.defaults.headers.common['Authorization'] = AUTH_TOKEN;


//配置优先级(逐级递增)
// 使用由库提供的配置的默认值来创建实例
// 此时超时配置的默认值是 `0`
var instance = axios.create();

// 覆写库的超时默认值
// 现在,在超时前,所有请求都会等待 2.5 秒
instance.defaults.timeout = 2500;

// 为已知需要花费很长时间的请求覆写超时设置
instance.get('/longRequest', {
  timeout: 5000
});

 

7、cookie,sessionStorage,localStorage的区别

特性cookielocalStoragesessionStorageindexedDB
数据生命周期一般由服务器生成,可以设置过期时间除非被清理,否则一直存在页面关闭就清理除非被清理,否则一直存在
数据存储大小4K5M5M无限
与服务端通信每次都会携带在 header,中,对于请求性能影响不参与不参与不参与
游览器关闭如果不设置Expires的属性那么Cookie的存活时间就是在关闭浏览器的时候。游览器关闭消失单游览器页面关闭消失IndexedDB 是一种底层 API,用于在客户端存储大量的结构化数据

补充:cookie 原本并不是用来储存的,而是用来与服务端通信的

要使用cookie,页面必须在服务器中运行,直接双击打开页面无法使用cookie。

Cookie的后端

 

会话级别的Cookie:默认的关闭了浏览器Cookie就销毁了

持久级别的Cookie:需要设置有效时长,关闭浏览器也不会销毁的Cookie

setMaxAge(int expiry):
以秒为单位的时间,超过了该时间后Cookie会自动销毁
setMaxAge(0):
手动删除持久性的Cookie。(前提path和那么必须一致)
setPath(Stirng uri):
设置Cookie的有效路径
例如:
1)cookie.setPath("/JavaEEDemo/learn/demo");
表示JavaEEDemo项目下,learn目录下的所有servlet,都可以访问当前cookie
“/learn"或”/learn/aaa"将不能访问
2)cookie.setPath("/JavaEEDemo");
表示JavaEEDemo项目下的所有servlet都可以访问当前cookie
3)cookie.setPath("/");
表示tomcat下的所有web项目,都可以访问当前cookie
cookie的唯一表示:

唯一表示:domain + path + name (类似Java中 包 + 类名)
domain 域名,不同的网站使用的是不同的域名,cookie就不同
path 路径,通过cookie.setPath()设置的内容
name cookie的名称,通过 new Cookie(name,…)确定的内容
例如:以下表示的是两个Cookie,可以同时存在
1)/web/a/b/cookieName
2)/web/acookieName
如果路径和名称一样,两个addCookie()后者将覆盖前者。

JAVA设置HttpOnly Cookies

HttpOnly Cookies是一个cookie安全行的解决方案。

在支持HttpOnly cookies的浏览器中(IE6+,FF3.0+),如果在Cookie中设置了"HttpOnly"属性,那么通过JavaScript脚本将无法读取到Cookie信息,这样能有效的防止XSS攻击,让网站应用更加安全。

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;

/**
 * Cookie工具类
 */
public class CookieUtil {

    /**
     * 设置HttpOnly Cookie
     * @param response HTTP响应
     * @param cookie Cookie对象
     * @param isHTTPOnly 是否为HttpOnly
     */
    public static void addCookie(HttpServletResponse response, Cookie cookie, boolean isHttpOnly) {
        String name = cookie.getName();//Cookie名称
        String value = cookie.getValue();//Cookie值
        int maxAge = cookie.getMaxAge();//最大生存时间(毫秒,0代表删除,-1代表与浏览器会话一致)
        String path = cookie.getPath();//路径
        String domain = cookie.getDomain();//域
        boolean isSecure = cookie.getSecure();//是否为安全协议信息 

        StringBuilder buffer = new StringBuilder();

        buffer.append(name).append("=").append(value).append(";");

        if (maxAge == 0) {
            buffer.append("Expires=Thu Jan 01 08:00:00 CST 1970;");
        } else if (maxAge > 0) {
            buffer.append("Max-Age=").append(maxAge).append(";");
        }

        if (domain != null) {
            buffer.append("domain=").append(domain).append(";");
        }

        if (path != null) {
            buffer.append("path=").append(path).append(";");
        }

        if (isSecure) {
            buffer.append("secure;");
        }

        if (isHttpOnly) {
            buffer.append("HTTPOnly;");
        }

        response.addHeader("Set-Cookie", buffer.toString());
    }

}

Cookie前端

1.cookie存在document.cookie中。

2.删除cookie则设置cookie的过期时间为当前时间-1。

3.设置cookie时需要对value进行编码,即使用escape函数,取出时需要进行解码,即使用unescape函数。

注:escape() 函数可对字符串进行编码,这样就可以在所有的计算机上读取该字符串。

//cookie存储值
function setCookie(cName,cValue,exDays){
    var d = new Date();
    d.setTime(d.getTime()+(exDays*24*60*60*1000));
    var expires = "expires="+d.toGMTString();
    document.cookie = cName+"="+cvalue+"; "+expires;
}
//cookie读取值
function getCookie(cName){
    var name = cName + "=";
    var ca = document.cookie.split(';');
    for(var i=0; i<ca.length; i++) {
        var c = ca[i].trim();
        if (c.indexOf(name)==0) { return c.substring(name.length,c.length); }
    }
    return "";
}
//检查cookie值
function checkCookie(){
    var user=getCookie("username");
    if (user!=""){
        alert("欢迎 " + user + " 再次访问");
    }
    else {
        user = prompt("请输入你的名字:","");
          if (user!="" && user!=null){
            setCookie("username",user,30);
        }
    }
}
//删除cookie值
function delCookie(name){
    var d = new Date();
    d.setTime(d.getTime() + 5*60*1000); // in milliseconds
    document.cookie = 'name=;path=/;expires='+d.toGMTString()+';';
    //等同于
    //document.cookie = 'name=;path=/;max-age='+5*60+';';
}

localStorage (window.localStorage)

  1. localStorage 只能存字符串,存取 JSON 数据需配合 JSON.stringify() 和 JSON.parse()
  2. 遇上禁用 setItem 的浏览器,需要使用 try...catch 捕获异常
  3. localStorage 的使用也是遵循同源策略的
  4. localStorage 只支持 string 类型的存储。

localStorage 的局限

  •  1、浏览器的大小不统一,并且在 IE8 以上的 IE 版本才支持 localStorage 这个属性。
  •  2、目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对我们日常比较常见的JSON对象类型需要一些转换。
  •  3、localStorage在浏览器的隐私模式下面是不可读取的。
  •  4、localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡。
  •  5、localStorage不能被爬虫抓取到。
//-------三种存储值写法---------
if(!window.localStorage){
    alert("浏览器不支持localstorage");
    return false;
}else{
    var storage=window.localStorage;
    //写入a字段
    storage["a"]=1;
    //写入b字段
    storage.b=1;
    //写入c字段
    storage.setItem("c",3);

    //-----三种读取方法------
    //第一种方法读取
    var a=storage.a;
    console.log(a);
    //第二种方法读取
    var b=storage["b"];
    console.log(b);
    //第三种方法读取
    var c=storage.getItem("c");

    //--------清除---------
    storage.clear();
    //--------删除-----------
     storage..removeItem(key)
    //---------获取-------------
    for(var i=0;i<storage.length;i++){
        var key=storage.key(i);
        console.log(key);
    }
}

sessionStorage  

sessionStorage 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。

----语法

//获取sessionStorage
var sessionStorage = window.sessionStorage;
//保存数据
sessionStorage.setItem("key", "value");
//读取数据
var lastName = sessionStorage.getItem("key");
//删除数据
sessionStorage.removeItem("key");
//删除所有数据
sessionStorage.clear();

indexedDB (H5新增,Web SQL)

(1)存储数据量非常大,在理论上基本上不封顶。具体的上限按照磁盘进行计算,理论上是可以磁盘空间的50%

(2)是文档型数据库,与Mongodb等类似,与常见的关系型数据库不同,不支持SQL语句查询。

(3)支持且只支持transaction事务,对保证数据安全具有重要意义,与传统数据库中的事务目的一致。

(4)遵循同源策略,所以网页只能访问同域的IndexedDB数据库,而无法访问其他域上的数据。

(5)大多数情况下API采用异步操作,以防止数据操作量大时,阻塞其他操作的进行。(同步暂时大部分的浏览器暂不支持)

(6)不是以表的形式存储数据,而是通过对象仓库存储。(可以理解为一张表,但从存储结构上并不一样)

(7)数据库除了存储字符串外,还可以存储Date, Object, Arra, File, Blob, ImageData或二进制等数据类型。

(8)能够为数据建立索引,以此提高查询数据的各方面性能。(这个非常有用,是大数据快速查找的前提)

备注:

注意indexeddb已经内置到浏览器中,无需其他依赖包和三方库。在浏览的调试情况下,本地缓存的展示位置如下图。

 

8、什么是闭包,为什么用闭包

闭包就是能够读取其他函数内部变量的函数,定义在一个函数内部的函数。本质上就是将函数内部和函数外部连接起来的一座桥梁。

使用闭包原理:即重用一个变量,又保护变量不被污染的一种机制。

 1. 用外层函数包裹要保护的变量和内层函数。

 2. 外层函数将内层函数返回到外部。

3. 调用外层函数,获得内层函数的对象,保存在外部的变量中——形成了闭包。  

function f1(){

    var n=999;

    function f2(){
      alert(n); 
    }

    return f2;

  }
var result=f1();

result(); // 999

 

9、数组的方法

10、字符串包含判断

11、数组的去重

1)、ES6 set方法:Array.form(new Set(array))

2)、循环对比

function unique(arr){            
        for(var i=0; i<arr.length; i++){
            for(var j=i+1; j<arr.length; j++){
                if(arr[i]==arr[j]){         //第一个等同于第二个,splice方法删除第二个
                    arr.splice(j,1);
                    j--;
                }
            }
        }
return arr;
}

12、谈谈This对象的理解

13、js如何实现继承的

1)1.原型链继承

function Parent () {
  this.names = ['kevin', 'daisy'];
}
function Child () {
 
}
 
Child.prototype = new Parent();
 
var child1 = new Child();
 
child1.names.push('yayu');
 
console.log(child1.names); // ["kevin", "daisy", "yayu"]
 
var child2 = new Child();
 
console.log(child2.names); // ["kevin", "daisy", "yayu"]

2)借用构造函数(经典继承)

function Parent () {
  this.names = ['kevin', 'daisy'];
}
 
function Child () {
  Parent.call(this);
}
 
var child1 = new Child();
 
child1.names.push('yayu');
 
console.log(child1.names); // ["kevin", "daisy", "yayu"]
 
var child2 = new Child();
 
console.log(child2.names); // ["kevin", "daisy"]

3)、

14、vue首页白屏

原因:

1)打包后文件引用路径不对,导致找不到文件报错白屏

解决办法:修改一下config下面的index.jsbulid模块导出的路径。因为index.html里边的内容都是通过script标签引入的,而你的路径不对,打开肯定是空白的。先看一下默认的路径。

2)由于把路由模式mode设置影响,删除mode或者把mode改成hash就OK了。

3)项目中使用了es6的语法,一些浏览器不支持es6,造成编译错误不能解析而造成白屏

  • 安装 npm install --save-dev babel-preset-es2015
  • 安装 npm install --save-dev babel-preset-stage-3
  • 在项目根目录创建一个.babelrc文件 里面内容 最基本配置是:
{
    // 此项指明,转码的规则
    "presets": [
        // env项是借助插件babel-preset-env,下面这个配置说的是babel对es6,es7,es8进行转码,并且设置amd,commonjs这样的模块化文件,不进行转码
        ["env", {
            "modules": false
        }],
        // 下面这个是不同阶段出现的es语法,包含不同的转码插件
        "stage-2"
    ],
    // 下面这个选项是引用插件来处理代码的转换,transform-runtime用来处理全局函数和优化babel编译
    "plugins": ["transform-runtime"],
    // 下面指的是在生成的文件中,不产生注释
    "comments": false,
    // 下面这段是在特定的环境中所执行的转码规则,当环境变量是下面的test就会覆盖上面的设置
    "env": {
        // test 是提前设置的环境变量,如果没有设置BABEL_ENV则使用NODE_ENV,如果都没有设置默认就是development
        "test": {
            "presets": ["env", "stage-2"],
            // instanbul是一个用来测试转码后代码的工具
            "plugins": ["istanbul"]
        }
    }
}

4)、第四种,如果你的测试说你的项目在 ios 10 出现白屏问题

  • 进入build文件夹;
  • 找到webpack.prod.conf.js文件;
  • 在UglifyPlugin的定义里添加关于mangle的选项,使它变成下面这个样子:
 new UglifyJsPlugin({
      uglifyOptions: {
          compress: {
            warnings: false
          },
          mangle: {
            safari10: true
          }
        },
      sourceMap: config.build.productionSourceMap,
      parallel: true
}),

5、使用不支持ES6的方法,比如Swiper,更改

module.exports = {
  chainWebpack: config => {
    config.rule('js').include.add(/node_modules\/(dom7|swiper)\/.*/)
  }
}
 

加载慢的解决方法:

1)、路由的懒加载

2)、ui框架按需加载(ui里的模块)

3)、gizp压缩

//下载资源
npm i compression-webpack-plugin -D

//vue.config.js配置
const CompressionPlugin = require("compression-webpack-plugin")

module.exports = {
  configureWebpack: () => {
    if (process.env.NODE_ENV === 'production') {
      return {
        plugins: [
          new CompressionPlugin({
            test: /\.js$|\.html$|\.css$|\.jpg$|\.jpeg$|\.png/, // 需要压缩的文件类型
            threshold: 10240, // 归档需要进行压缩的文件大小最小值,我这个是10K以上的进行压缩
            deleteOriginalAssets: false, // 是否删除原文件
            minRatio: 0.8
          })
        ]
      }
    }
  }
}

4、loading

首页加一个loading或许是最原始的方法了,在index.html里加一个loadingcss效果,当页面加载完成消失,友好用户体验感

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值