vivo 悟空活动中台 - H5 活动加载优化

2023-11-10

本文首发于 vivo互联网技术 微信公众号 
链接: https://mp.weixin.qq.com/s/6gtVR0nVNcZvREjwftZgzA
作者:悟空中台研发团队

【悟空活动中台】系列往期精彩文章:

一、背景

通过之前悟空活动中台系列文章,大家对微组件、动态布局等技术方案有了一定的了解。本篇我们带大家了解下悟空H5专题性能优化之路。

在移动互联网时代,H5页面加载体验至关重要。消费者行为和观念也会受到页面加载时间的产生显着影响,最明显的就是我们现在很难去等待一个页面加载超过三秒的页面,尤其是年轻人。专注性能测试的SOASTA公司曾发表过结论:移动端加载每耗时1秒, 影响转化率最高可达 20%。

在营销中台业务快速发展过程中,悟空始终把网站响应速度和用户体验放在第一位,通过技术创新,不断寻找最优加载方案,取得了很好的效果。下面我们就一起来探索下。

二、优化历程

每谈到性能优化,前端er就能联想到一道经典面试题:从输入URL到页面加载,浏览器都执行了什么?

体验优化的历程和这道题一样,需要系统化梳理、体系化实践。我们从网络、资源、渲染、执行层出发,不断探索加载优化方案。

1、网络层优化

(1)DNS 处理:增加 dns-prefetch

浏览器对网站第一次的域名 DNS 解析查找流程依次为:浏览器缓存 >> 系统缓存 >> 路由器缓存 >> ISP DNS 缓存 >> 递归搜索。

移动端环境下,DNS 请求带宽非常小,但延迟很高。针对该问题,我们采取预读取DNS方案,该方案能显著降低延迟,平均加载时长可减少1秒左右。

为帮助浏览器对某些域名进行预解析,我们对上线活动 html 文档中新增 dns-prefetch标签。加入该标签后,浏览器解析步骤如下:

第一步:用 meta 信息来告知浏览器,当前页面要做 DNS 预解析:

<meta http-equiv="x-dns-prefetch-control" content="on" />

第二步:在页面 header 中使用 link 标签来强制对 DNS 预解析:

<link rel="dns-prefetch" href="//topicstatic.vivo.com.cn" />

悟空在上线H5资源需要根据不同区域,生成不同的dns-prefetch地址,编译活动脚手架link标签新增逻辑如下:

<% if (国内活动) {%>
  <link rel="dns-prefetch" href="//topic.vivo.com.cn">
  <link rel="dns-prefetch" href="//cmsapi.vivo.com.cn">
  <link rel="dns-prefetch" href="//topicstatic.vivo.com.cn">
  <% } else if(印度活动) {%>
  <link rel="dns-prefetch" href="//in-goku.vivoglobal.com">
  <link rel="dns-prefetch" href="//topicstatic.vivo.com.cn">
  <link rel="dns-prefetch" href="//in-gokustatic.vivoglobal.com">
  <% } else { %>
  <link rel="dns-prefetch" href="//asia-goku.vivoglobal.com">
  <link rel="dns-prefetch" href="//asia-gokustatic.vivoglobal.com">
  <link rel="dns-prefetch" href="//asia-wukongapi.vivoglobal.com">
<% } %>

(2) CDN 分发优化

CDN 的全称是 Content Delivery Network,即内容分发网络。CDN 是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。

下图展示终端用户访问页面时,CDN获取过程:

缓存对于CDN服务至关重要,合适的缓存策略能够降低源站的请求压力,从而提升页面加载速度,因此我们需要优化静态资源存储方式和缓存策略。

CDN资源缓存配置如下:

 

悟空将H5专题的静态资源上传至CDN,带来如下提升:

  • 通过 CDN 向用户分发传输相关库的静态资源文件,可以降低我们自身服务器的请求压力。

  • 大多数 CDN 在全球都有服务器,所以 CDN上的服务器在地理位置上可能比你自己的服务器更接近你的用户。用户直接访问边缘缓存,极大地提升页面资源的响应速度。

  • 不缓存HTML入口文件,只缓存js、css的策略,避免资源不更新的同时,加快了专题资源的获取速度。

不缓存HTML入口文件的目的是防止客户端缓存策略,导致主入口资源不更新,导致线上升级失败。

(3)HTTP/2

HTTP/2 的定义为:

(超文本传输协议第 2 版,最初命名为HTTP 2.0),简称为h2(基于 TLS/1.2 或以上版本的加密连接)或h2c(非加密连接)[1],是HTTP协议的的第二个主要版本,使用于万维网

将 HTTP 消息分解为独立的帧,交错发送,然后在另一端重新组装是 HTTP 2 最重要的一项增强。事实上,这个机制会在整个网络技术栈中引发一系列连锁反应,从而带来巨大的性能提升:

 

_

1.0

1.1

2.0

长连接

需要使用keep-alive 参数来告知服务端建立一个长连接

默认支持

默认支持

HOST 域

不支持

支持

支持

多路复用

不支持

-

支持

数据压缩

不支持

不支持

使用HAPCK算法对 header 数据进行压缩,使数据体积变小,传输更快

服务器推送

不支持

不支持

支持

HTTP2.0开启方式如下:

server {  
 listen        443 **ssl** **http2**;  
  server_name   yourdomain;
  ……  
  ssl          on**;
  …… 
}

开启 HTTP 2监听:

listen 443 ssl http2;

多路复用代替原有的序列以及阻塞机制,使得多个资源可以在一个连接中并行下载,不受浏览器同一域名资源请求限制,提升整站的资源加载速度。

(4)动态字体压缩

字体文件大小普遍在2M左右,H5活动页面字体量有限,但仅仅为少量特殊文字全量引入字体文件,页面性能损耗非常大。与此同时,由于营销活动的复杂性与多样性,单纯的图片字体很难满足多变的运营需求。

寻找满足字体多样性的同时,保证字体大小,是平台需攻克的技术难点,最终,我们探索出一套适用平台的动态字体压缩方案。

字体压缩,也可以被称为字体子集化,可以理解为通过特定方式将中英文字从大字体文件中剥离,组合成小字体文件供页面使用。

概念看上去有点抽象,我们先直观感受下压缩前后效果:

接下来会重点讲述悟空基于业务场景的字体压缩方案,压缩字体的核心诉求是:可压缩字体文件,可动态更换文本内容进行压缩。

基于悟空微组件动态打包上线方式,我们选择使用 fontmin 来完成动态压缩字体。

动态压缩字体分为以下几个步骤:

第一步,读取特定配置文件中的 id,预先请求到对应页面接口数据,进行数据归集处理。部分代码示例:

const request = require('request')
request(url,  (error, response, data) => {
  if (error) {                  
    console.error(err);
    return
  }
  const res = JSON.parse(data)
  if (res.code === 0) {
    //获取专题配置数据
    const config = JSON.parse(URLDecode(res.data.config))
    const pages = config.pages
    let str = ''
    const familyList = new Set()
    pages.forEach(page => {
      const items = page.items
      items.forEach(item => {
        //根据配置,拼接需加载字体的字符串和字体类型
        if (item.pluginInfo.enName === 'site-text') {
          str += item.pluginConfig.pureText
          familyList.add(item.pluginConfig.typeFace)
        }
      })
    });
    //处理字体
    handleFont(str, familyList)
  }
});

第二步,遍历字体类型列表 familyList,利用 fontmin 进行字体文件压缩。这一步要求我们预先将字体的本地文件放入编译脚手架中。在压缩的同时,需要通过webpack插件来生成对应的 css 文件:

字体动态压缩处理逻辑:


const compressFont = (fontText, fontName) => {
  const srcPath = `dist/${siteId}/font/${fontName}.ttf`; 
  const destPath = `dist/${siteId}/compressFont`;   

  const fontmin = new Fontmin()
    .src(srcPath)               // 输入配置
    .use(Fontmin.glyph({        // 字形提取
      text: fontText            // 动态注入文字
    }))
    .use(Fontmin.ttf2eot())     // eot转换
    .use(Fontmin.ttf2woff())    // woff转换    
    .use(Fontmin.ttf2svg())     // svg转换
    .use(Fontmin.css({
      fontPath: `/compressFont/`,
      fontFamily: fontName,  
    }))       
    .dest(destPath);            // 输出文件

  fontmin.run(function (err, files, stream) {
    if (err) {                  
      console.error(err);
      return
    }
    // 读取生成后的对应的 css 文件内容并合成
    const fontCss = fs.readFileSync(path.join(__dirname, `../dist/${siteId}/compressFont/${fontName}.css`)).toString()
    fontStyleStr += fontCss
    loadHtml(fontStyleStr)
  })
}

const handleFont = (fontText, familyList) => {
  familyList.forEach(name => {
    compressFont(fontText, name)
  })
}

2、资源优化

(1)图片懒加载

图片懒加载是一种很好的优化网页或应用的方式,它能够在用户滚动页面时自动获取更多的数据,新获取的图片不会影响到页面呈现,同时视口外的图片有可能永远不需要被加载,能够极大的节约用户流量以及服务器资源。'

懒加载的一般形式表现为:

  1. 打开首页,滑动页面

  2. 懒加载图片展示默认图

  3. 默认图替换为真实图片

根据悟空现有的技术栈,我们选择vue-lazyload 去支撑位组件的图片来加载:

  • 对 vue 的原生支持,平台扩展后所有组件都可使用

  • 方便快捷的指令式开发,img 标签的 src 改为 v-lazy 就可以实现图片懒加载

  • 功能符合预期,支持背景图片懒加载,支持图片 url 动态修改为 webp

悟空提供给组件开发者资源懒加载指令,用户无需感知具体的加载逻辑,通过悟空的内置能力即可实现专题图片懒加。具体用法如下:


<template>
  <div>
    <img v-lazy="imgUrl" />
    <div v-lazy:background-image="imgUrl"></div>

    <!-- with customer error and loading -->
    <img v-lazy="imgObj" />
    <div v-lazy:background-image="imgObj"></div>

    <!-- Customer scrollable element -->
    <img v-lazy.container="imgUrl" />
    <div v-lazy:background-image.container="img"></div>

    <!-- srcset -->
    <img
      v-lazy="'img.400px.jpg'"
      data-srcset="img.400px.jpg 400w, img.800px.jpg 800w, img.1200px.jpg 1200w"
    />
    <img
      v-lazy="imgUrl"
      :data-srcset="imgUrl' + '?size=400 400w, ' + imgUrl + ' ?size=800 800w, ' + imgUrl +'/1200.jpg 1200w'"
    />
  </div>
</template>
<script>
export default {
  data() {
    return {
      imgObj: {
        src: 'http://xx.com/logo.png',
        error: 'http://xx.com/error.png',
        loading: 'http://xx.com/loading-spin.svg',
      },
      imgUrl: 'http://xx.com/logo.png', // String
    }
  },
}
</script>

(2)图片压缩

在移动端环境下,图片加载一直是需要重点优化的关键项,所以才延伸出懒加载这种交互方案来提高用户体验。

当该方案优化到了落地后,我们下一步考虑如何在保证图片质量的前提下,尽量压缩图片体积,提升图片加载效率。

WebP 是 Google 推出的一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式。相比于其他相同大小不同格式的压缩图像,WebP 格式的图片拥有更小的体积以及更高的质量,所以它的优势十分明显。

WebP 是 Google 推出的一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式。相比于其他相同大小不同格式的压缩图像,WebP 格式的图片拥有更小的体积以及更高的质量,所以它的优势十分明显。

在使用 WebP 进行有损压缩后,我们大概可以将原本的图片大小压缩至原来的十分之一左右,而图片质量却没有大的损失。这确实是一个惊人的效率。

我们可以看下一组数据来看下 webp 有损压缩效果:

Webp 有损压缩(75%质量比)

await execFileSync(cwebp, ['-q', '75', filePath, '-o', webpPath]);

原大小

压缩时间(ms)

压缩后大小

999kb

237

38kb

999kb

221

38kb

999kb

228

38kb

999kb

228

38kb

999kb

261

38kb

在转换结束后,悟空会将原图片和转换后的 webp 图片都上传到 cdn 上,做一个备份的能力,实际业务场景可以根据需求去选择是否使用 Webp 图片。

下图展示 Webp 压缩前后效果,右侧展示压缩后图片,图片大小从215k减小至17k。

悟空在使用 Webp 压缩时,也遇到种种问题,如下:

  • 为什么悟空选择 75% 的压缩质量?

  • 什么特征的图片不适合Webp压缩?

  • 部分图片压缩后资源变大

后续文章《悟空活动中台 - 基于Webp的图片高效加载方案》会详细叙述悟空如何从平台角度提供 Webp压缩方案。

(3)跨域避免 option 请求

悟空H5专题采用的是前后端分离方案,服务器域名和专题域名不一致,会受到浏览器同源策略影响。

我们发现数据主接口会发起两次,其中第一个请求为预检请求。

一般来说使用 application/json 的 post 请求是必然会带入 OPTION 请求,何为 OPTION 预检:

用于获取目的资源所支持的通信选项。客户端可以对特定的 URL 使用 OPTIONS 方法,也可以对整站(通过将 URL 设置为“*”)使用该方法。

 CORS 中,可以使用 OPTIONS 方法发起一个预检请求,以检测实际请求是否可以被服务器所接受。预检请求报文中的 Access-Control-Request-Method 首部字段告知服务器实际请求所使用的 HTTP 方法;Access-Control-Request-Headers 首部字段告知服务器实际请求所携带的自定义首部字段。服务器基于从预检请求获得的信息来判断,是否接受接下来的实际请求。

有趣的是专题详情为 GET 接口,为何 GET 请求也会发起 option 预检?

这个原因得从简单请求和复杂请求说起,跨域请求分为简单和复杂两种:

简单请求:

请求方式为如下之一:

HEAD

GET

POST

HTTP 请求头只能包含如下信息:

Accept

Accept-Language 

Content-Language 

Last-Event-ID 

Content-Type,但仅能是下列之一 

application/x-www-form-urlencoded 

multipart/form-data 

text/plain

任何一个不满足上述要求的请求,即被认为是复杂请求。一个复杂请求不仅有包含通信内容的请求,同时也包含预检信息。

专题配置接口请求头中带有自定义 header,浏览器会认定为非简单请求,需要向服务器发出检查,判断该域名是否允许跨域。

经过分析发现,自定义 header 其实在此业务场景中非必传自带,发出预检请求至少会有 100ms 的耗时,无形中延长页面绘制时间。

最终解决方案:去除自定义header,修改为简单请求,避免该请求发出预检。

3、渲染执行优化

在网络层以及资源压缩优化落地后,接下来探索浏览器渲染执行优化点,涉及到浏览器,一定会联想到网页解析过程,下图清晰的展示静态资源如何通过浏览器最终显示:

当dom元素变化会导致浏览器重新执行渲染树生成、绘制,我们称之为重排重绘。

什么是重排?当 render tree 中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为重排(回流)。每个页面至少需要一次回流,就是在页面第一次加载的时候。

(1)避免重排

浏览器结构示意图:

可以看到浏览器有负责解析、渲染请求内容的渲染引擎,哪些动作会导致浏览器重排:

(1)增加或删除 DOM 节点;

(2)display:none(重排并重绘); visibility:hidden(重绘);

(3)移动页面中的元素;

(4)改变元素尺寸(宽、高、内外边距、边框等);

(5)用户改变窗口大小,滚动页面等;

(6)页面初始渲染;

(7)改变元素内容(文本或图片等)。

offsetTop, offsetLeft,...
scrollTop, scrollLeft, ...
clientTop, clientLeft, ...
getComputedStyle() (currentStyle in IE)

这些属性都需要实时回馈给用户的几何属性或者是布局属性,浏览器不得不立即执行渲染队列中的“待处理变化”,并随之触发重排返回正确的值。

document.body.style.minWidth = '12OOpx'
document.body.style.overflow = 'hidden'
//获取某div的偏移量
document.querySelector('xxx').offsetTop

我们优化活动代码执行逻辑,将上述直接操作 dom 的操作修改为 class 样式操作,减少加载过程中重复的dom操作。

(2)善用 Vue 生命周期

善用 Vue 组件生命周期,在合适的 hook 去初始化数据,操作dom,能够大幅提升加载体验。

在mounted 阶段,浏览器已经完成 dom 与 css 规则树的 render,并完成 render tree布局,这时候再去发送数据请求,会拉长请求时间和渲染周期,所以建议在beforeCreate中执行,以此达到预渲染和请求的并行进行。

我们将活动初始化数据的动作放在 beforeCreate 阶段,并将对 dom 的操作和监听挂载在 mounted 中。

{
  beforeCreate(){
    fetch({
      url: topicUrl,
      params: {
        //...
      }
    }).then(res=>{
      //数据处理
      //...
    })
  },
  mounted() {
    // global listener
    window.addEventListener('xxx');
    // get dom element by refs
    this.$refs.xxx
    // get dom element use native api
    document.querySelector
  }
}

对浏览器来说,整个渲染流程尚未开始或者说准备开始,对 vue 来说,实例尚未被初始化,data observer 和 event/watcher 也还未被调用,这个时候请求页面初始化数据时机是比较成熟的。

(3)减少白屏时间

相比 Native 页面,H5 页面体验问题主要是:打开一个 H5 页面需要做一系列处理,会有一段白屏时间,体验糟糕。

白屏时间是指浏览器从响应用户输入网址地址,到浏览器开始显示内容的时间。

本次专题优化,我们采用如下方式去减少白屏时间:

  • 骨架屏,html直接渲染过渡效果

  • 改造第三方 JS 引入顺序

  • 使用 SplitChunksPlugin 拆分公共代码;

  • 使用动态 import,切分页面代码,减小首屏 JS 体积

其中改造骨架的方式是一种成本低,效果非常卓越的方式,更进阶的方式有服务端直出等。由于悟空活动专题有快,灵的特点,配置改变需实时生效,所以前期我们权衡方案利弊,采用骨架,直接渲染过渡效果的方案。

页面加载html后直接显示加载效果,在底版本andriod手机中,webwiew初始化过程会有一个高度切换过程,加载后出现Native的titleBar,导致过渡效果会产生位置移动场景。

为了解决该问题,我们使用css3动画来实现过渡效果延迟出现,避免与webview初始化冲突。

animation: loading 1s linear 300ms infinite;
···
@keyframes loading{
  from {
    opacity: 1;
  }
  to {
    opacity: 1;
  }
}

这一现象能侧面反映出,loading出现基本于webview初始化同期进行,速度很快。为了解决loaidng瞬移的问题,我们采用纯css3实现loading延迟出现,不与webview初始化冲突。

三、优化成果

1、同一专题优化前后数据对比

下述表格展示同一微组件和配置的活动在整体优化前后网站整体体验评分,评分来自PageSpeed Insights。

国内活动

优化前

优化后

首次绘制

2.8s

1.3s

速度指数

4s

3.8s

绘制耗时

12s

2.3s

综合得分(满分 100)

44

90

海外活动

优化前

优化后

首次绘制

3.5s

1.3s

速度指数

5.6s

3.3s

绘制耗时

3.5s

2.8s

综合得分(满分 100)

67

92

2、国内活动效果

相同配置专题:

 

3、海外活动效果

相同配置专题:

四、性能数据收集

1、常用指标

关于指标,业界有非常多的方案和数据:

  • 页面加载时长

  • 首屏加载时长

  • Dom Ready 时长

  • Dom Complete 时长

  • 首页渲染时长

  • 首页内容渲染时长

  • 首页有效渲染时长

  • .......

基于活动的特点以及业务常关注点:我们对页面白屏时间以及首次渲染时长以及一些个性化指标进行了收集,目的是统计活动专题加载时长,寻找优化空间。

2、如何计算

静态资源的加载速度,可以利用 performance Timing API 取得

up-0e3ede15c2713635a1a300a8e3f777e2644.png

白屏时间:

白屏时间 = 开始渲染时间(首字节时间+HTML 下载完成时间)= responseStart - navigationStart

首次渲染时长 = 全部事件注册时长 = loadEventEnd - navigationStart

页面绘制时间=获取数据到加载结束 = loadEventEnd - fetchEnd(自行记录)

3、上报方法

关于性能数据的上报方式,平台使用 sendBeacon 进行无阻塞性能数据上报

navigator.sendBeacon() 方法可用于通过HTTP将少量数据异步传输到 Web 服务器。

这个方法主要用于满足统计和诊断代码的需要,发送代码通常尝试在卸载(unload)文档之前向 web 服务器发送数据。

function stat() {
  navigator.sendBeacon('/path', analyticsData)
}

sendBeacon 发出的是异步请求,请求作为浏览器任务执行,与当前页面脱钩。因此该方法不会阻塞页面加载流程,也不会延迟页面加载。

五、思考与展望

在上述探索的同时,我们同时在进行专题 SSR 、秒开、CSR的方案探索,不断尝试提升 H5 体验的方式,追求卓越。

在笔者看来,性能优化不是一种手段,而是一种意识,开发者在实际开发过程中需要建立意识,在各处细节上去保证用户体验。

六、参考文献

  1. https://developers.google.com/web/fundamentals/performance/http2?hl=zh-cn

  2. https://juejin.im/entry/56ce7d1a1532bc005372a7fa

更多内容敬请关注 vivo 互联网技术 微信公众号

注:转载文章请先与微信号:Labs2020 联

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

vivo 悟空活动中台 - H5 活动加载优化 的相关文章

  • CORS 问题。 Flask <-> AngularJS

    使用 angularjs 客户端应用程序和提供 api 的 Flask 应用程序启动一个新项目 我使用 mongodb 作为数据库 我必须立即排除 jsonp 因为我需要能够跨不同端口进行 POST 因此 我们为 Angular 应用程序设
  • 滚动在 chrome 中的 svg 异物内不起作用

    我在 svg 异物中有 div 带有溢出 auto 滚动仅适用于鼠标滚轮 无法拖动栏 它适用于 Firefox 但不适用于 chrome safari 如果 svg g 元素上没有转换 它就可以工作 参见小提琴 http jsfiddle
  • 如何使用 jest 测试 unhandledRejection / uncaughtException 处理程序

    我有处理程序unhandledRejections and uncaughtExceptions bin js unhandledRejection uncaughtException forEach event gt process on
  • 使用 AngularJs NgResource 从本地主机加载 JSON 文件

    Overview 我正在构建一个应用程序 在 MAMP 上运行 该应用程序包含联系信息 一旦该部分正常运行 该应用程序将扩展以包含更多数据 例如项目名称和截止日期 问题 当用户访问时 projects php project 我希望他们看到
  • 有没有比 setTimeout 更准确的方法来创建 Javascript 计时器?

    一直困扰我的是事情的不可预测性setTimeout Javascript 中的方法是 根据我的经验 计时器在很多情况下都非常不准确 我所说的不准确是指实际延迟时间似乎或多或少有 250 500 毫秒的差异 尽管这并不是一个很长的时间 但当使
  • 无限水平滚动 Div

    我需要一个 div 当您将鼠标进一步向右或向左移动时 它会水平滚动 我发现 Smooth Div Scroll 插件 http www smoothdivscroll com 非常接近我的需要 然而 这存在一些问题 我需要能够使滚动元素从设
  • 将 C# 3D 数组移植到 JS 3D 数组

    我开发了一个 C 库 它在 3 个项目中使用 这些项目在特定的代码段中中继 不过 我仍然需要在 javascript 中使用该代码 所以将其导出 问题是 我认为我无法复制同样的逻辑 比如这个问题我想了好几天也没有得出答案 在 C 库中 我有
  • 打开一个新的浏览器窗口/iframe 并在 TEXTAREA 中从 HTML 创建新文档?

    我正在尝试使用 HTML5 的新离线功能编写一个 Web 应用程序 在此应用程序中 我希望能够编辑一些 HTML 完整文档 而不是片段
  • 使用 jquery UI 调整大小的分屏 div

    我心中有一个设计 涉及 html 中的拆分面板视图 类似于 winforms 拆分面板 我一直在尝试jQuery UI 可调整大小 http jqueryui com demos resizable我喜欢这个功能 我只是似乎无法协调两者的大
  • .addClass 仅添加到无序列表中单击的项目符号

    我有一个简短的无序列表 其中有两个项目符号 我添加了一些 Javascript 这样当我单击项目符号时 它会向其中添加一个类 问题是 它将该类添加到所有现有的 li 中 而不仅仅是我单击的那个 这是 JSFiddle http jsfidd
  • 单击链接时如何将另一个 JSP 页面注入到

    我在一个JSP页面中有两个不同的部分 其中一个包含链接菜单 单击时 div2 id content 会相应加载不同的页面 我正在做类似的事情 div ul class navbar li a href Login jsp Login a l
  • 解密签名并验证 JWT

    我知道还有其他库可以让我更轻松地使用 JWT 在 Node js 中 在本例中 我使用 crypto js 以手动方式学习 JWT 以下给了我令牌 var header alg HS256 typ JWT var wordArrayHead
  • Node.js 中的 SetTimeout 问题

    我有以下代码 它在 Chrome V8 下运行良好 但在节点内失败 var id id setTimeout TimeoutHandler 10 console log SET function TimeoutHandler clearTi
  • 缩放对象上的弹跳动画

    拥有对象比例 然后在返回到原始比例因子之前以该比例因子执行弹跳动画的最佳方法是什么 我意识到我可以做一些事情 比如将其缩放到 2 2 然后 1 8 然后 2 0 但我正在寻找一种方法 您只需在比例因子上执行弹跳动画 因为我的比例因子会改变
  • 重写node.js中其他模块中的函数

    我正在尝试在 Node js 应用程序中使用 nodeunit 存根函数 这是我正在尝试做的事情的简化版本 In lib file js var request require request var myFunc function inp
  • SVG 沿圆弧添加文本

    我正在尝试绘制 SVG 径向饼图 如下所述 色卡 https stackoverflow com a 18210763 1395178 现在我尝试将文本与圆弧一起添加到每个切片 我试图展示Text 1具有与 M 和 A 值完全相同的 x y
  • PhoneGap文件传输错误1、哪里写FileTransfers?

    相关 https stackoverflow com questions 21044197 download file and store them locally in sdcard using phonegapbuild https s
  • javascript:新日期,缺少年份

    我打电话给 new Date Jan 4 发现默认年份是2001年 a new Date Jan 4 Thu Jan 04 2001 00 00 00 GMT 0500 EST 有什么办法可以将默认年份设置为 2011 年吗 更新 我知道我
  • 无法使用 javascript 建立与安全 Websocket 服务器的连接

    我的开发环境是这样的 操作系统 微软Windows 10 PHP 框架 Laravel 8 0 PHP 版本 7 4 Websocket 服务器 cboden ratchet 0 4 3 WAMP 服务器 3 2 0 Apache 2 4
  • 获取类的公共属性而不创建它的实例?

    假设我们有一个 JavaScript 类 var Person function function Person name surname this name name this surname surname Person prototy

随机推荐

  • PyQt4编程之如何做菜单栏

    菜单栏是大部分软件都有的 菜单栏能提供便捷的帮助 记事本的菜单栏就是最简单的一个例子 等过几天我会写记事本的菜单栏了再另外发代码出来 下面的代码是Copy的 import sys from PyQt4 import QtGui QtCore
  • Python2,python3调用face++api

    由于官网给的api只能支持python2 然而自己改成3的话特别麻烦 花了两三天都没有改好 查阅各种资料都没有结果 今天偶遇一代码 非常感谢这位博主 现将其代码和我的使用样例献上 希望能够帮助到和我一样的小白 该博主的代码 Face API
  • 用Lex(flex)和yacc(bison)写的简单计算器

    Lex文件如下 include cal tab h option noyywrapinteger 0 9 dreal 0 9 0 9 ereal 0 9 0 9 EedD 0 9 real dreal ereal nl nplus minu
  • 使用ipv6内网穿透,实现私有云盘搭建,实现远程控制等功能

    文章目录 问题 获得计算机的ipv6地址 ipv6变化问题 解决 桌面远程控制 ipv6控制路由器 解决 私有云盘搭建 创建服务端B的环境配置 创建服务端可以访问的用户账户 配置服务器对ipv6地址访问的监听 创建ipv6访问客户端 NAT
  • ubuntu使用docker安装jdk和tomcat (一)

    Docker是一个开源的引擎 可以轻松的为任何应用创建一个轻量级的 可移植的 自给自足的容器 开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署 包括VMs 虚拟机 bare metal OpenStack 集群和其他的基础应用平
  • IP地址的组成和划分(3)

    文章目录 一 IP地址组成 二 IP地址的划分 1 A类地址 2 B类地址 3 C类地址 4 D类地址 5 E类地址 一 IP地址组成 IP地址由4部分数字组成 每部分数字对应于8位二进制数字 各部分之间用小数点分开 这是点分二进制 如果换
  • 解决mysql不是内部或外部命令 环境变量 并且

    安装Mysql后 当我们在cmd中敲入mysql时会出现 Mysql 不是内部或外部命令 也不是可运行的程序或其处理文件 打开我的电脑在我的电脑右键中选择属性 然后单击选择高级系统设置 在系统属性的 高级 中选择环境变量path 选择Mys
  • 宏观经济浅学20210711

    M2 大类资产配置 GDP 周期 https www bilibili com video BV1V5411e76f from search seid 3336101618933886388 宏观经济站把控 统治地位 宏观经济研究 经济为什
  • 三合一浴霸必须一直接通取暖开关才能控制照明和风扇的解决方法

    刚新租了一个二室房子 入住后发现这样一个奇怪的问题 要想只让三合一 照明 取暖 通风 浴霸只照明或者只通风 必须得把取暖打开才行 并且取暖必须一直打开 否则一旦断开 照明和取暖也用不了了 头一次遇见这样的问题 首先怀疑是否是浴霸就是这么设计
  • Kotlin IO操作

    前段时间学习了一点内容 写了一篇Groovy开发工具包 我当时就在想Kotlin怎么没有好用的文件操作API呢 后来我发现我太傻了 Kotlin这么好用的语言怎么可能没有自己的文件API呢 Kotlin的IO操作都在kotlin io包下
  • 数据结构习题解析与实验指导-严蔚敏数据结构-第三章:栈和队列(刷题记录)

    目录 第三章 栈和队列 刷题记录 P 48 49 第一题 2022年4月15日 星期五 晚上19 20 19 35 第三章 栈和队列 刷题记录 P 48 49 第一题 2022年4月15日 星期五 晚上19 20 19 35 算法思想 两栈
  • allegro 丝印 对齐_Cadence Allegro 17.2高级功能- Label Tune 批量字符对齐功能

    Allegro的全称是Cadence Allegro PCB Designer 是Cadence公司推出的一个完整的高性能印制电路板设计套件 通过顶尖的技术 它为创建和编辑复杂 多层 高速 高密度的印制电路板设计提供了一个交互式 约束驱动的
  • UE中UPROPERTY部分说明符

    在UE的C 编程中 通常使用UPROPERTY EditAnywhere 宏将属性公开给UE编辑器 使得可以在UE编辑器中对这些属性进行修改 避免了多次编译的繁琐 这个宏也有一些说明符 不同的含义 EditAnywhere 括号中必须有这个
  • oracle 11g在安装过程中出现监听程序未启动或数据库服务未注册到该监听程序

    在使用Database configuration Assistant创建数据库时 在创建到85 的时候报错 错误提示内容如下 错误分析 经过查看警告中给出的日志文件 F develop oracle data app Administra
  • PHP 7Ghost实现反向代理功能,不需要Nginx,支持替换

    虽然Nginx可以方便 简单地实现网站反向代理 但是也存在一定的不方便 此处省略1000字 7ghost是一款基于PHP的网站反向代理 反向绑定域名 程序 能够快速高效的反向代理所指定的网站 并拥有丰富的内容替换 请求头设置 7Ghost这
  • MSP430F5529——中断理解

    认识低功耗模式 MSP430的中断 需要两个部分 一部分是打开中断 另外一部分是编写中断服务函数 打开中断 BIS SR与 bis SR register 首先我们得知道 bis SR register和 BIS SR是一个玩意 查看宏定义
  • RedisTemplate中opsForValue的使用

    Spring 封装了 RedisTemplate 对象来进行对redis的各种操作 它支持所有的 redis 原生的 api 查阅点资料下面总结看下Redis中opsForValue 方法的使用介绍 1 set K key V value
  • PyQt5 资源加载总结

    一 概述 在Qt Designer中要使用图片资源有三种方法 通过图像文件指定 通过资源文件指定 通过theme主题方式指定 对应的设置界面在需要指定图像的属性栏如QLabel 的pixmap 属性通过点击属性设置栏的倒三角按钮触发 如下图
  • springboot部署成jar包后的启动,停止,重启脚本

    注 不知道出处在哪里 不过测试后可以使用 文章目录 脚本内容 给脚本权限 使用方式 脚本内容 bin bash 这里可替换为你自己的执行程序 其他代码无需更改 APP NAME home application processes proc
  • vivo 悟空活动中台 - H5 活动加载优化

    本文首发于 vivo互联网技术 微信公众号 链接 https mp weixin qq com s 6gtVR0nVNcZvREjwftZgzA 作者 悟空中台研发团队 悟空活动中台 系列往期精彩文章 揭秘 vivo 如何打造千万级 DAU