探索前端黑科技——通过 png 图的 rgba 值缓存数据
本文系原创,欢迎转载,转载请注明作者信息
项目地址:SphinxJS
在线体验地址:https://jrainlau.github.io/sp...
说起前端缓存,大部分人想到的无非是几个常规的方案,比如cookie
,localStorage
,sessionStorage
,或者加上indexedDB
和webSQL
,以及manifest
离线缓存。除此之外,到底还有没有别的方法可以进行前端的数据缓存呢?这篇文章将会带你一起来探索,如何一步一步地通过png图的rgba值来缓存数据的黑科技之旅。
PS:本文所研究的内容已经整合成一个开源的JS库,名字叫SphinxJS,感兴趣的同学可以移步到这篇文章SphinxJS——把字符串编码成png图片的超轻量级开源库去看相关的文档,欢迎STAR!
原理
我们知道,通过为静态资源设置Cache-Control
和Expires
响应头,可以迫使浏览器对其进行缓存。浏览器在向后台发起请求的时候,会先在自身的缓存里面找,如果缓存里面没有,才会继续向服务器请求这个静态资源。利用这一点,我们可以把一些需要被缓存的信息通过这个静态资源缓存机制来进行储存。
那么我们如何把信息写入到静态资源中呢?canvas
提供了.getImageData()方法和.createImageData()方法,可以分别用于读取和设置图片的rgba
值。所以我们可以利用这两个API进行信息的读写操作。
接下来看原理图:
当静态资源进入缓存,以后的任何对于该图片的请求都会先查找本地缓存,也就是说信息其实已经以图片的形式被缓存到本地了。
注意,由于rgba
值只能是[0, 255]之间的整数,所以本文所讨论的方法仅适用于纯数字组成的数据。
静态服务器
我们使用node
搭建一个简单的静态服务器:
const fs = require('fs')
const http = require('http')
const url = require('url')
const querystring = require('querystring')
const util = require('util')
const server = http.createServer((req, res) => {
let pathname = url.parse(req.url).pathname
let realPath = 'assets' + pathname
console.log(realPath)
if (realPath !== 'assets/upload') {
fs.readFile(realPath, "binary", function(err, file) {
if (err) {
res.writeHead(500, {'Content-Type': 'text/plain'})
res.end(err)
} else {
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'image/png',
'ETag': "666666",
'Cache-Control': 'public, max-age=31536000',
'Expires': 'Mon, 07 Sep 2026 09:32:27 GMT'
})
res.write(file, "binary")
res.end()
}
})
} else {
let post = ''
req.on('data', (chunk) => {
post += chunk
})
req.on('end', () => {
post = querystring.parse(post)
console.log(post.imgData)
res.writeHead(200, {
'Access-Control-Allow-Origin': '*'
})
let base64Data = post.imgData.replace(/^data:image\/\w+;base64,/, "")
let dataBuffer = new Buffer(base64Data, 'base64')
fs.writeFile('assets/out.png', dataBuffer, (err) => {
if (err) {
res.write(err)
res.end()
}
res.write('OK')
res.end()
})
})
}
})
server.listen(80)
console.log('Listening on port: 80')
这个静态资源的功能很简单,它提供了两个功能:通过客户端传来的base64生成图片并保存到服务器;设置图片的缓存时间并发送到客户端。
关键部分是设置响应头:
res.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'image/png',
'ETag': "666666",
'Cache-Control': 'public, max-age=31536000',
'Expires': 'Mon, 07 Sep 2026 09:32:27 GMT'
})
我们为这张图片设置了一年的Content-Type
和十年的Expires
,理论上足够长了。下面我们来进行客户端的coding。
客户端
<!-- client.html -->
<canvas id="canvas" width="8", height="1"></canvas>
假设我们需要存储的是32位的数据,所以我们为canvas设置宽度为8,高度为1。到底为什么32位数据对应长度为8,是因为每一个像素都有一个rgba
,对应着red
,green
,blue
和alpha
4个数值,所以需要除以4。
<!-- client.js -->
let keyString = '01234567890123456789012345678901'
let canvas = document.querySelector('#canvas')
let ctx = canvas.getContext('2d')
let imgData = ctx.createImageData(8, 1)
for (let i = 0; i < imgData.data.length; i += 4) {
imgData.data[i + 0] = parseInt(keyString[i]) + 50
imgData.data[i + 1] = parseInt(keyString[i + 1]) + 100
imgData.data[i + 2] = parseInt(keyString[i + 2]) + 150
imgData.data[i + 3] = parseInt(keyString[i + 3]) + 200
}
ctx.putImageData(imgData, 0, 0)
首先我们假设需要被缓存的字符串为32位的01234567890123456789012345678901
,然后我们使用.createImageData(8, 1)
生成一个空白的imgData对象。接下来,我们对这个空对象进行赋值。为了实验效果更加直观,我们对rgba
值都进行了放大。设置完了imgData
以后,通过.putImageData()
方法把它放入我们的canvas即可。
我们现在可以打印一下,看看这个imgData
是什么:
// console.log(imgData.data)
[50, 101, 152, 203, 54, 105, 156, 207, 58, 109, 150, 201, 52, 103, 154, 205, 56, 107, 158, 209, 50, 101, 152, 203, 54, 105, 156, 207, 58, 109, 150, 201]
接下来,我们要把这个canvas编译为一张图片的base64并发送给服务器,同时接收服务器的响应,对图片进行缓存:
$.post('http://xx.xx.xx.xx:80/upload', { imgData: canvas.toDataURL() }, (data) => {
if (data === 'OK') {
let img = new Image()
img.crossOrigin = "anonymous"
img.src = 'http://xx.xx.xx.xx:80/out.png'
img.onload = () => {
console.log('完成图片请求与缓存')
ctx.drawImage(img, 0, 0)
console.log(ctx.getImageData(0, 0, 8, 1).data)
}
}
})
代码很简单,通过.toDataURL()
方法把base64发送到服务器,服务器处理后生成图片并返回,其图片资源地址为http://xx.xx.xx.xx:80/out.png
。在img.onload
后,其实图片就已经完成了本地缓存了,我们在这个事件当中把图片信息打印出来,作为和源数据的对比。
结果分析
开启服务器,运行客户端,第一次加载的时候通过控制台可以看到响应的图片信息:
200 OK
,证明是从服务端获取的图片。
关闭当前页面,重新载入:
200 OK (from cache)
,证明是从本地缓存读取的图片。
接下来直接看rgba
值的对比:
源数据: [50, 101, 152, 203, 54, 105, 156, 207, 58, 109, 150, 201, 52, 103, 154, 205, 56, 107, 158, 209, 50, 101, 152, 203, 54, 105, 156, 207, 58, 109, 150, 201]
缓存数据:[50, 100, 152, 245, 54, 105, 157, 246, 57, 109, 149, 244, 52, 103, 154, 245, 56, 107, 157, 247, 50, 100, 152, 245, 54, 105, 157, 246, 57, 109, 149, 244]
可以看到,源数据与缓存数据基本一致,在alpha
值的误差偏大,在rgb
值内偶有误差。通过分析,认为产生误差的原因是服务端在进行base64转buffer的过程中,所涉及的运算会导致数据的改变,这一点有待考证。
之前得到的结论,源数据与缓存数据存在误差的原因,经查证后确定为alpha
值的干扰所致。如果我们把alpha
值直接定为255,并且只把数据存放在rgb
值内部,即可消除误差。下面是改良后的结果:
源数据: [0, 1, 2, 255, 4, 5, 6, 255, 8, 9, 0, 255, 2, 3, 4, 255, 6, 7, 8, 255, 0, 1, 2, 255, 4, 5, 6, 255, 8, 9, 0, 255]
缓存数据:[0, 1, 2, 255, 4, 5, 6, 255, 8, 9, 0, 255, 2, 3, 4, 255, 6, 7, 8, 255, 0, 1, 2, 255, 4, 5, 6, 255, 8, 9, 0, 255]
因为我懒,只是把alpha
值给定为255而没有把循环赋值的逻辑进行更新,所以第4n位的元数据被直接替换成了255,这个留着读者自行修改有空再改……
综上所述,这个利用png图的rgba值缓存数据的黑科技,在理论上是可行的,但是在实际操作过程中可能还要考虑更多的影响因素,比如设法消除服务端的误差,采取容错机制等。实际上也是可行的。
值得注意的是,localhost
可能默认会直接通过本地而不是服务器请求资源,所以在本地实验中,可以通过设置header进行cors
跨域,并且通过设置IP地址和80端口模拟服务器访问。
后记
说是黑科技,其实原理非常简单,与之类似的还有通过Etag
等方法进行强缓存。研究的目的仅仅为了学习,千万不要作为非法之用。如果读者们发现这篇文章有什么错漏之处,欢迎指正,也希望有兴趣的朋友可以一起进行讨论。
感谢你的阅读。我是Jrain,欢迎关注我的专栏,将不定期分享自己的学习体验,开发心得,搬运墙外的干货。下次见啦!
探索前端黑科技——通过 png 图的 rgba 值缓存数据的更多相关文章
- C++的黑科技(深入探索C++对象模型)
周二面了腾讯,之前只投了TST内推,貌似就是TST面试了 其中有一个问题,“如何产生一个不能被继承的类”,这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一个单例模式,但面试官 ...
- 一文带你了解 HTTP 黑科技
这是 HTTP 系列的第三篇文章,此篇文章为 HTTP 的进阶文章. 在前面两篇文章中我们讲述了 HTTP 的入门,HTTP 所有常用标头的概述,这篇文章我们来聊一下 HTTP 的一些 黑科技. HT ...
- [自己动手玩黑科技] 1、小黑科技——如何将普通的家电改造成可以与手机App联动的“智能硬件”
NOW, 步 将此黑科技传授予你~ 一.普通家电控制电路板分析 普通家电,其人机接口一般由按键和指示灯组成(高端的会稍微复杂,这里不考虑) 这样交互过程,其实就是:由当前指示灯信息,按照操作流程按相应 ...
- C++的黑科技
周二面了腾讯,之前只投了TST内推,貌似就是TST面试了 其中有一个问题,"如何产生一个不能被继承的类",这道题我反反复复只想到,将父类的构造函数私有,让子类不能调用,最后归结出一 ...
- localStorage的黑科技-js和css缓存机制
一.发现黑科技的起因 今天在微信公众号看到一篇技术博文,想用印象笔记收藏,所以发送了文章链接到pc上.然后习惯性地打开控制台,看看源码,想了解下最近微信用了什么新技术. 呵呵,以下勾起了我侦探的欲 ...
- Cnblogs关于嵌入js和css的一些黑科技
#pong .spoiler{cursor:none;display:inline-block;line-height:1.5;}sup{cursor:help;color:#3BA03B;} Pon ...
- Python3实现ICMP远控后门(中)之“嗅探”黑科技
ICMP后门 前言 第一篇:Python3实现ICMP远控后门(上) 第二篇:Python3实现ICMP远控后门(上)_补充篇 在上两篇文章中,详细讲解了ICMP协议,同时实现了一个具备完整功能的pi ...
- 【转载】史上最全:TensorFlow 好玩的技术、应用和你不知道的黑科技
[导读]TensorFlow 在 2015 年年底一出现就受到了极大的关注,经过一年多的发展,已经成为了在机器学习.深度学习项目中最受欢迎的框架之一.自发布以来,TensorFlow 不断在完善并增加 ...
- 这些JavaScript编程黑科技
1.单行写一个评级组件 "★★★★★☆☆☆☆☆".slice(5 - rate, 10 - rate);定义一个变量rate是1到5的值,然后执行上面代码,看图 才发现插件什么的都 ...
随机推荐
- SQL:查询时给表起别名
Q 有两个表student.score,查询前一个表的id.name列,后一个表的total列,查询结果通过两个表的id连接起来 要求 使用别名st和sc替换表名进行查询 A SELECT st.id ...
- c语言——uthash使用
参考:https://troydhanson.github.io/uthash/userguide.html https://blog.csdn.net/lovemust/article/detail ...
- LeetCode-039-组合总和
组合总和 题目描述:给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合. candidates 中的数 ...
- Redis原理再学习05:数据结构-整数集合intset
intset介绍 intset 整数集合,当一个集合只有整数元素,且元素数量不多时,Redis 就会用整数集合作为集合键的底层实现. redis> SADD numbers 1 3 5 7 9 ...
- 总结一下Java基础知识
重中之重-----------基础 底层结构决定上层建筑,所以,基础很重要. 自信心 在开始说基础之前先说明一下自信心的重要性,没有自信心学什么东西都学不好. 核心自信***** 核心自信从三个方面来 ...
- Java学习笔记:01面向对象-继承
01面向对象-继承 一.java中的数据类型 1.基本数据类型:四类 八种 byte(1) boolean(1) short(2) char(2) int(4) float(4) long(8) do ...
- LGP3281口胡
当你看到一个东西的时候,GF 有可能比 DP 更方便.处理贡献也有可能比 DP 更方便. 这个题意明显是让我们计算 \(S(r)-S(l-1)\) 之类的东西( 所以直接考虑前缀的答案就好了( 考虑将 ...
- CentOS7.5环境下安装配置GitLab
1. 安装依赖软件 yum -y install policycoreutils openssh-server openssh-clients postfix 2.设置postfix开机自启,并启动, ...
- 线程池的极简用法——内置线程池multiprocessing
大家好,今天博主来分享一个线程池的小捷径--内置线程池的使用方法 一.背景 说道多线程,对变成层有了解的小伙伴一定不陌生,虽然不知道是什么但是也会从各大网站.面试分享等途径听说过.这里就不做过多的介绍 ...
- linux下查看文件编码及修改编码介绍
1.在Vim中可以直接查看文件编码:set fileencoding即可显示文件编码格式.如果你只是想查看其它编码格式的文件或者想解决用Vim查看文件乱码的问题,那么你可以在~/.vimrc 文件中添 ...