shader 编程是图形学编程中的重要环节,在 shader 代码中可以实现各种各样简单复杂的计算机图形学算法。本文通过列举一些简单的 shader 代码实例,看看能不能激发你学习的shader编程的兴趣。此外shader 的灵活性让我们除了实现各种传统的算法,还可以发挥想象力实现更多有趣的图形。
这不是一个从零开始的着色器教程,关于着色器网络上也有很多优秀的资料教程,读者可以提前搜索一下。不过这里还是带大家了解一下和本文相关的基础知识。
uniform [变量类型] [变量名称]
这种方式定义的变量,其值不是由着色器来决定的,而是由外部环境通过某种方式传入的。例如web中用js通过的webGL Api或者是桌面端c++通过openGL Api等等。所以这种变量在着色器中通常都是拿过来用的只读变量。
全局变量
每一个实例都是由图片 + 代码 + 小段解释 + 在线demo组成。
precision highp float; // 声明float的精度 // 外部环境与shader单向通信的变量// 这个变量直接在shader中使用就好了uniform vec2 u_size; // 当前画布的尺寸void main() { vec2 rg_size = gl_FragCoord.xy / u_size; // x,y分量限制在[0,1] gl_FragColor = vec4( 0.0, rg_size.x, rg_size.y, 1.0 );}
gl_FragCoord表示的是当前片元着色器处理的片元(暂时理解为像素)的坐标,是一个vec4类型的变量(x, y, z, 1/w),目前你只需要知道gl_FragCoord.xy表示取出第1和2两个分量,分别表示水平坐标x和纵坐标y(坐标原点位于左下角)。
这一段代码非常的简单,首先通过除以画布尺寸将坐标限制在[0, 1]的范围之内,然后将得到的结果的xy分量分配给保留的全局变量gl_FragColor。也就是,无论你前面进行了多么复杂的计算过程,最终都是要通过这一个全局变量来将颜色输出,否则你的着色器程序是没有意义的。
关于颜色分布,我们可以看几个特殊点——四个角。左下角vec4(0.0, 0.0, 0.0, 1.0)黑色,左上角vec4(0.0, 0.0, 1.0, 1.0)蓝色,右下角vec4(0.0, 1.0, 0.0, 1.0)绿色,右上角vec4(0.0, 1.0, 1.0, 1.0)青色。
你可以尝试调换0.0、rg_size.x、rg_size.y三者的位置,看看会得到什么样不同的颜色分布。
precision highp float; // 声明float的精度#define PI 3.14159265359 // 外部环境与shader单向通信的变量uniform vec2 u_size; // 当前画布的尺寸uniform float u_dpr; // 绘制环境的devicePixelRatio// 定义一个函数// value 待测值// target 目标值float smoothstep_filter(float value, float target){ return smoothstep(target - 0.01, target, value) - smoothstep(target, target + 0.01, value);}void main() { float rate = 100.0 * u_dpr; vec2 st = gl_FragCoord.xy / vec2(rate); // 减小画布坐标数值范围 vec3 color = vec3(0.0); // 黑色背景 vec3 line_color = vec3(38.0, 204.0, 213.0) / vec3(255.0); float y = sin(st.x) + (u_size.y / rate) * 0.5; // 一个周期为2PI, 振幅为1的sin三角函数 // float y = tan(st.x) + (u_size.y / rate) * 0.5; // 一个周期为PI的tan三角函数 float percent = smoothstep_filter(st.y, y); color = mix(color, line_color, percent); gl_FragColor = vec4(color, 1.0);}
平时学习一些图像绘制的时候总是绕不开的一个东西就是三角函数曲线,以正弦为例,y = sin(x),这是一个周期为2PI,振幅为1的正弦函数。不过想要在shader中绘制这样一根简单的曲线,似乎还不那么直观。
首先,我们定义了一个函数plot用与判断当前坐标是否在目标值的范围内,返回一个[0, 1]之间平滑后的值。这里用到了一个shader内置的函数——smoothstep,用于生成平滑的插值。
然后在main函数当中调用plot函数得到颜色混合的百分比percent,最后再调用一个shader内置函数mix,该函数用于线性插值。将插值得到的颜色赋给gl_FragColor。
同样你可以尝试不同的三角函数,例如余弦函数、正切函数等等。
precision highp float; // 声明float的精度#define PI 3.14159265359// JS与shader单向通信的变量uniform vec2 u_size; // 当前画布的尺寸uniform float u_dpr; // 绘制环境的devicePixelRatio// 根据输入的value和目标value// 返回位于目标点[0.0, +half_pi]范围内的突变值float step_filter(float value, float taget_value){ return step(taget_value, value) - step(taget_value + 0.2, value);}// color patternvec3 color_1 = vec3(45.0, 89, 198) / vec3(255.0);vec3 color_2 = vec3(49, 142, 222) / vec3(255.0);vec3 color_3 = vec3(38, 205, 213) / vec3(255.0);vec3 color_4 = vec3(118, 224, 214) / vec3(255.0);void main() { vec2 st = gl_FragCoord.xy/u_size.xy; vec3 color = vec3(0.0); vec2 pos = vec2(0.5) - st; // 中心指向当前坐标点的向量 float distance = length(pos)*2.0; // 中心点到当前坐标点的距离 float alpha = atan(pos.y,pos.x); // 当前位置对应的旋转角 float f = tan(alpha * 5.); // 当前角度对应的函数值 color = mix(color, color_1, step_filter(distance, f)); // 比较实际距离 distance 和函数值 f 的大小 gl_FragColor = vec4(color, 1.0); // 进而影响当前像素的最终颜色}
绘制极坐标曲线的关键在于得到当前点到坐标原点的距离distance以及目标距离值f,然后通过step_filter函数获取需要插值的值,最后用mix函数进行插值,将得到的颜色赋值给gl_FragColor。
这里用到的极坐标表达式为 f = tan(alpha * 5),你还可以尝试其他的函数例如 f = 1.0,这将绘制一个正圆。
precision highp float; // 声明float的精度#define PI 3.14159265359// JS与shader单向通信的变量uniform vec2 u_size; // 当前画布的尺寸// 2D Randomfloat random (in vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);}// 2D Noise based on Morgan McGuire @morgan3d// https://www.shadertoy.com/view/4dS3Wdfloat noise (in vec2 st) { vec2 i = floor(st); vec2 f = fract(st); // Four corners in 2D of a tile float a = random(i); float b = random(i + vec2(1.0, 0.0)); float c = random(i + vec2(0.0, 1.0)); float d = random(i + vec2(1.0, 1.0)); // Smooth Interpolation // Cubic Hermine Curve. Same as SmoothStep() vec2 u = f*f*(3.0-2.0*f); // u = smoothstep(0.,1.,f); // Mix 4 coorners percentages return mix(a, b, u.x) + (c - a)* u.y * (1.0 - u.x) + (d - b) * u.x * u.y;}void main() { vec2 st = gl_FragCoord.xy/u_size.xy; // Scale the coordinate system to see // some noise in action vec2 pos = vec2(st*400.0); // Use the noise function float n = noise(pos); gl_FragColor = vec4(vec3(n), 1.0);}
这个例子是关于噪声的。我们首先定义了两个函数,一个random函数,用于根据一个二维向量生成一个float随机值,然后定义了一个noise函数,用于根据二维向量生成一个float噪声值。
然后,我们通过noise函数来对放大后的实际坐标pos进行干扰,将干扰得到的结果n,赋值给gl_FragColor,最终出来的效果如上图(笔者感觉很像小时候看的彩电没有型号的时候的画面)。
// https://www.shadertoy.com/view/lsX3W4precision mediump float;uniform vec2 u_size;uniform float u_time;float fixedTime = u_time / 2.0;float distanceToMandelbrot(in vec2 c){#if 1 { float c2 = dot(c, c); // skip computation inside M1 - http://iquilezles.org/www/articles/mset_1bulb/mset1bulb.htm if (256.0 * c2 * c2 - 96.0 * c2 + 32.0 * c.x - 3.0 < 0.0) return 0.0; // skip computation inside M2 - http://iquilezles.org/www/articles/mset_2bulb/mset2bulb.htm if (16.0 * (c2 + 2.0 * c.x + 1.0) - 1.0 < 0.0) return 0.0; }#endif // iterate float di = 1.0; vec2 z = vec2(0.0); float m2 = 0.0; vec2 dz = vec2(0.0); for (int i = 0; i < 300; i++) { if (m2 > 1024.0) { di = 0.0; break; } // Z' -> 2·Z·Z' + 1 dz = 2.0 * vec2(z.x * dz.x - z.y * dz.y, z.x * dz.y + z.y * dz.x) + vec2(1.0, 0.0); // Z -> Z² + c z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; m2 = dot(z, z); } // distance float d = 0.5 * sqrt(dot(z, z) / dot(dz, dz)) * log(dot(z, z)); if (di > 0.5) d = 0.0; return d;}void main(){ vec2 p = (2.0 * gl_FragCoord.xy - u_size.xy) / u_size.y; // animation float tz = 0.5 - 0.5 * cos(0.225 * fixedTime); float zoo = pow(0.5, 13.0 * tz); vec2 c = vec2(-0.05, .6805) + p * zoo; // distance to Mandelbrot float d = distanceToMandelbrot(c); // do some soft coloring based on distance d = clamp(pow(4.0 * d / zoo, 0.2), 0.0, 1.0); vec3 col = vec3(d); gl_FragColor = vec4(col, 1.0);}
这个例子放在这里是觉得很有趣,且为了图形分类的完整性。有兴趣深入研究其算法的可以看看这篇https://www.iquilezles.org/www/articles/distancefractals/distancefractals.htm。(这段代码我就不解释,因为笔者也没怎么看懂)
precision mediump float;uniform vec2 u_size;uniform float u_time;float fixedTime = u_time / 2.0;float width_1 = 2.0;float width_2 = 2.0;float f_x = 100.0;float f_y = 100.0;void main(){ vec3 color = vec3(1.0); // x方向上满足 if(mod(gl_FragCoord.x, f_x) <= width_1) { color = vec3(0.0); } // y方向上满足 if(mod(gl_FragCoord.y, f_y) <= width_2) { color = vec3(0.0); } gl_FragColor = vec4(color, 1.0);}
pattern通常是由周期性重复的图案组成,这里的例子很简单,周期性最小重复单元可以看作是一个黑色的方框。
主要逻辑分为两个部分,一个是x方向,另一个是y方向。用到了条件if语句和内置求余函数mod。该实例建议打开demo链接调节几个参数看看实际的效果会发生什么变化。
precision highp float; // 声明float的精度// 与JS单向通信的变量uniform vec2 u_size; // 当前画布的尺寸uniform float u_time; // 一个随时间变化的量void main() { vec2 rg_size = gl_FragCoord.xy / u_size; // x,y分量限制在[0,1] gl_FragColor = vec4( abs(sin(u_time)), // 加入时间系数 rg_size.x, rg_size.y, 1.0 );}
截止到目前的例子都是静态的图形,在静态的基础上加上一个随时间变化的量u_time即可作出简单的动画效果。
这里我们以最简单的Color Map为例,将之前的0.0替换为 abs(sin(u_time)) 即可得到一个简单的颜色渐变动画效果。如果是更加复杂的动画效果思路都是类似的——将时间系数引入到颜色计算的过程当中去。
看完了上面的“基础”,我们很容易想到将某些方式进行组合,也许会产生一些新的效果。下面就是笔者自己组合出的一些结果,有些挺出乎意料的。(代码都比较长,而且与前面的实例有很大部分重叠,就不放代码了,看代码直接去到链接。)
ps:这线段上的四种颜色你眼熟吗(狗头)
组合方式远不止笔者列举出的这些,更多可能性留给读者自己去探究。
shader编程思想和语法相对稳定,学习了shader编程你既可以给webGL编写shader也可以给openGL编写shader,甚至是游戏引擎环境下编写shader等等。并且其对于像素的控制能力可以让你做出很多惊人的效果。
当然了,我们学习shader编程并非只是是为了写这些“花哨”的图形,而是让你对于渲染有了更强的控制能力。
]]>Q:为什么一篇全中文的文章要用英文标题?
A:因为“在web中设计web”,“在网页中设计网页”、“基于网页的设计工具”、“在网页中做设计”、“在浏览器中设计网页”等等都显得这篇文章将要讲一个古老的东西,而事实上我想讲的东西应该并不古老。
这篇文章主要讲解,在2020年,基于web的UI设计工具Figma到底能做到什么程度(在我的认识里Figma是基于web的UI设计工具中的佼佼者,如果有比这更强的,请务必告知)。
在我发现Figma之前一直使用的是macOS独有的UI设计工具Sketch,Sketch也是一个很棒的UI设计工具,它专注于UI设计(而非像photoshop一样的图形处理)。Sketch还提出了symbol的概念,这类似于前端开发中“组件”思想,基于symbol你可以构建可复用的设计素材甚至是设计系统、设计规范。例如,很多有名的组件库,国内的antd,google的material design等等都提供了Sketch格式的组件库,这给基于相关组件库设计的设计师提供了便捷。
总之Sketch大概有这些优点:专注于UI设计、矢量绘制、symbol功能、丰富的插件生态。
说完了这些优点,再讲一讲Sketch的缺点。
花了不小的篇幅来介绍Sketch是因为无对比无伤害,下面回到Figma。
第一次看到Figma的时候我还在美滋滋地使用Sketch,心想,你一个基于web的东西还能不卡?你一个基于web的东西功能还能不阉割?你一个基于web的东西能解决本地字体的调用问题?可是现实就是Figma反手给了我3个耳光。Figma不仅消除了我提到的这几个顾虑而且还解决得很好。
先说,性能
这一点是我以及很多人怀疑的一点,我本人也是主前端方向的,一直也都觉得基于web的应用不可能做得了什么“重”的工具。对于web来说,一个完整的UI设计工具已经称得上是“重”了。
Figma的性能出乎意料的好。下面是随手做的缩放测试,测试素材来自Material Design 2.0 Theming kit的Figma版本。(需要说明的是,由于录屏和转换gif的原因,实际的效果要比你看到的gif流畅得多)
components页面的32个画板,每一个画板上面都是有很多元素的:
全选32个画板,复制得到64画板:
居然还不卡,然后全选64个画板复制得到128个画板:
可以看到缩放仍然流畅。虽然仅仅测试缩放并不能完全体现性能的方方面面,但是足以说明问题。
再说,功能
前面讲的Sketch的所有的功能和优点Figma同样都有,只不过是名字不同罢了。例如symbol、矢量绘制、插件生态等等。所以说,功能上丝毫没有因为基于web而阉割。
本地字体调用
对于桌面软件来说,调用本本地的字体应该不算什么难事,但是对于基于web的应用,在浏览器中调用本地字体就没那么直接了。所以Figma专门为此开发了Figma Font Helper插件,可以让运行在浏览器的Figma直接调用本地字体。
至此,我所担忧的几个问题,似乎都被Figma解决了。但是仅仅解决我所担忧的只能证明他做到了“及格”,要想评价为“优秀”,这些是不够的。
Figma做到了及格是因为它满足了目前UI设计的基本需求。下面我们从与Sketch的对比中看看Figma为什么称得上优秀。
前文吐槽的Sketch的几个缺点,插件开发不友好、性能差、前后版本文件可能不兼容、分享设计不方便、不支持跨平台,Figma似乎都不存在这些缺点。这些也就自然而然成为了Figma的优点。
除了改正了Sketch的一些缺点以外,Figma还有一下自身的优点:
既然说到了设计,仅仅是UI设计是不够的,至少还有交互设计。所以这里也不得不提到的另一个原型工具Framer。恰好,Framer在前不久也发布了它的web版本。但是这篇文章不打算介绍它了,可以告知的是桌面版的Framer是一个很好的原型交互设计工具,web版的Framer也同样优秀。
Figma和Framer之间也可以无缝的配合,只需要在Figma中授权Framer,然后在Framer中粘贴Figma项目链接,之后就可以将你的设计导入到Framer中,继续进行下一步的交互设计工作。
如果你对构建Figma的技术细节感兴趣,可以访问他们的技术Blog,里面有很多和Figma相关的技术文章。由于作者被技术掐住了命运的咽喉,所以本文就暂时不再从技术层面进行分析了。
至此,基本的设计工具(我知道设计肯定不只是UI和交互)在web端算是完整了。设计师可以完全在浏览器中实现Design Web in the Web的目标,想一想觉得还是很有意思。基于文章的分析和对比,我认为未来Sketch是会被类似于Figma这样的基于web的设计工具所取代的,因为我完全找不到任何理由再继续使用Sketch。
目前有很多国内的公司还没使用Figma,原因我认为有下:
还记得我在第一次使用Figma的时候,它给我的感受是很惊艳的,倒不是说视觉设计惊艳,而是一个基于web的设计工具能做到这样的体验,甚至超越了基于桌面端的同类产品。作为一个前端开发者来说,我深知做出这样产品难度有多大。
如果你尝试过了Sketch、Adobe XD等一系列桌面端工具觉得还没找到最顺手的UI设计工具,不妨试试Figma!(Figma打钱!)
]]>文章同发于知乎专栏前端艺术(我认为这也是艺术,工程实现的艺术!),©️转载请注明出处,谢谢~
原文:https://tympanus.net/codrops/2019/02/20/how-to-create-a-fake-3d-image-effect-with-webgl/
学习如何用原生 WebGL 为具有深度映射的图像创建交互式伪 3D 效果。
demo: http://tympanus.net/Tutorials/Fake3DEffect/ [译者注:强烈建议你点进去看看!]
Get 源代码: http://tympanus.net/Tutorials/Fake3DEffect/Fake3DEffect.zip
如今,WebGL 变得非常流行,因为它让我们能够为 Web 创建独特的交互式图形。您可能已经看到使用Blotter.js 创建文字失真效果或使用THREE.MeshLine 库创建的 WebGL 动画曲线。今天,您将了解如何直接使用原生 WebGL 快速为图像创建交互式的伪 3D 效果。
如果使用 Facebook,则可能会看到新闻源和 VR 的 3D 照片更新。借助特殊的电话摄像头,可以捕捉前景中的对象与背景之间的距离,从而使 3D 照片通过景深和微小位移使场景栩栩如生。我们可以使用任何照片,一些图像编辑和一点点编码来重新创建这种效果。
通常,这类效果将依赖 Three.js 或 Pixi.js,这两个都是功能强大的库,在编码时具有许多有用且便捷的功能。今天,我们将不使用任何库,而是直接用原生的 WebGL API。
所以,赶快开始吧!
所以,为了实现这种效果,我们使用原生的 WebGL API. 这里有个让你入门 WebGL 的好地方 webglfundamentals.org. WebGL 通常因其冗长而遭到开发者的抱怨,其实,这是有原因的。所有全屏着色器效果(即使它们是 2D 效果)的基础是某种平面或网格,即所谓的四边形,它在整个屏幕上延伸。通常我们在 Three.js 中编写THREE.PlaneGeometry(1,1)
来创建 1×1 平面,而我们在原生 WebGL 中需要写:
let vertices = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1])let buffer = gl.createBuffer()gl.bindBuffer(gl.ARRAY_BUFFER, buffer)gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW)
现在我们有了平面,我们可以对其应用顶点和片段着色器。
为了创建 3D 效果,我们需要创建图像的深度图。制作深度图的主要原理是,我们必须根据图像中对象的 Z 坐标来分离图像由近到远的不同部分,从而将前景与背景分隔开。
为此,我们可以通过以下方式在 Photoshop 中打开图像并在原始照片上绘制灰色区域:
[译者注:其实这里的图片处理非常重要,将会直接影响到最后的效果。这里,我找到了一个[如何在 photoshop 中创建图像的深度图](https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=2ahUKEwjXkpWAroroAhVLL6YKHftmBMkQwqsBMAB6BAgKEAQ&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DLQ0RuWf-J1k&usg=AOvVaw2aUR2TWSNuh9NvqC3xLrML)的简易教程]此图显示了一些山脉,在这些山脉中您可以看到图中对象离相机越近,深度图中的区域绘制得越亮。在接下来的一部分中,让我们看看为什么这种阴影很有意义。## 编写着色器渲染逻辑主要发生在着色器中。如 MDN 网站文档所述:> 着色器是使用 OpenGL ES 着色语言(GLSL)编写的程序,该程序获取有关构成形状的顶点的信息,并生成将像素渲染到屏幕上所需的数据:即像素的位置及其像素颜色。绘制 WebGL 内容时,有两个着色器功能运行:顶点着色器和片段着色器。了解着色器的一个很好的资源是 [The Book Of Shaders](https://thebookofshaders.com/)。顶点着色器的作用在这并不复杂。它只显示顶点:attribute vec2 position; void main() { gl_Position = vec4(position, 0, 1);}
最有趣的部分将发生在片段着色器中。我们将在片段着色器中加载两张图片:void main(){ vec4 depth = texture2D(depthImage, uv); gl_FragColor = texture2D(originalImage, uv); // 只是显示原图}
需要指明的是,深度图图像是黑白的。对于着色器,颜色只是一个数字:1 是白色,0 是深黑色。uv 变量是一个二维映射,用于存储有关要显示哪个像素的信息。通过这两件事,我们可以使用深度信息稍微移动原始照片的像素。让我们从鼠标移动开始:vec4 depth = texture2D(depthImage, uv);gl_FragColor = texture2D(originalImage, uv + mouse); // 译者注:这里每一个顶点的位移都是完全相同的
[译者注:[关于 uv](http://wiki.winamp.com/wiki/Pixel_Shader_Basics#UV_Coordinates)]现在让我们添加深度信息:
vec4 depth = texture2D(depthImage, uv);gl_FragColor = texture2D(originalImage, uv + mouse*depth.r);
该见证奇迹了:
因为 depth 纹理是黑白的,所以我们可以采用红色通道depth.r
,并将其乘以屏幕上的鼠标位置值。这意味着像素越亮,跟随鼠标移动的像素就越多。另一方面,暗色像素只会保留在原位。它的原理是如此简单[译者注:真的简单易懂!],但是却可以产生令人印象深刻的 3D 图像错觉。
当然,着色器能够能够做的远远不止这,但是我希望你喜欢这个「伪造」的 3D 效果小实验。让我知道您对此有何想法,并希望借此来看看你的创作!
译者说:
这篇文章是继[译]使用 Three.js 制作有粘稠感的图像悬停效果的又一篇有意思的同类文章。翻译不是目的,因为有了 google translate 文章的英语不在话下,而且该文表达很简单直接,目的是分享我看到的值得分享的好的内容,我个人还是觉得这些东西很有意思的。
该文涉及到的 shader 代码并不复杂,可是思路非常巧妙,进而达到了不错的效果。而且最重要的是,只要你能制作出图像对应的深度图,那么就可以模拟出伪 3D 的效果。如果你能够将图中对象的前后关系颗粒度划分得越细,那么最终得到的效果应该是会越好。
2020.04.02更新
关于“深度图(depth map)”,最近发现除了人手动用photoshop处理以外,还可以借助训练好的(当然也可以自己训练)神经网络模型的力量来帮助你自动的从原图得到“深度图”。
比如我找到了以下的仓库:
在线生成“深度图”:
在线可交互深度图制作和预览:
虽然麻烦,但是我还是写了个自己的博客主题叫Paper
hexo的theme里面有接近300个主题,逛了一圈,似乎还是没有找到自己心仪的。看来看去,好像也就几个大的风格占了主要的比例。例如,Material Design、卡片布局、大图Landing,等等。
这些风格似乎都是近几年比较流行的博客风格,但可能是因为用的人太多,我有点审美疲劳了。趁着自己好像有点设计能力,决定写一个自己的博客主题。
这篇文章记录一下设计思想、设计过程、编码相关(其实这不是重点)。
Q&A
Q:主题是什么风格?
A:paper-like,随便翻译一下可以翻译为“类纸化”风格。主要体现在排版以及一些线条的使用上。排版借鉴了报纸的排版风格。
Q:具体参考了哪个报纸的排版设计?
A:我当时其实就是随便google了一张报纸,然后就开始设计了。链接我也给忘了,后面补上,长这样。
其实现在想起来,我当时应该多参考几个,不过已经有点晚了,这一点比较后悔。
其实除了借鉴真实报纸的排版以外,我还参考了另一个大名鼎鼎的设计站Anyway的设计,主要是其固定的边框设计(老实说我从未在其他任何网站见到过这种设计)、一宽一窄的边线设计。如果你喜欢了解设计相关的前沿信息,不妨访问Anyway设计站,他提供的信息质量都比较高。
Q:使用什么设计工具?
A:当下比较流行的UI矢量绘图工具sketch。就是一个画UI比较方便的绘图工具。用什么工具不太重要,画出来像你想的那样就行了。
Q:设计稿长什么样?
A:这样:
最终的真实页面并非和设计稿长得完全一样,因为中途编码的过程中一些小的想法有变化。并且个人感觉,也没必要完全和设计稿一样,尤其是当设计师和开发人员是同一个人的时候。。。
排版
如上所述,完全“模仿”和“魔改”的如图的报纸排版设计。作出好的排版实在是太难了,设计能力不够,所以就只有借鉴一下了。
字体
字体又是一个大坑,我看上的好的字体都要收费。无奈之下只能暂时选择中规中矩的通用方案font-family: "SF Pro Text","SF Pro Icons","Helvetica Neue","Helvetica","Arial",sans-serif
行高上我还是下了一定的功夫,因为行高对于阅读体验的影响比较大。参考了这位大佬博客上的行高算法,感觉最有道理,最能说服我。
按照上图的计算,最后将字号和行高设定为stylu中的变量
// font-size$font-size-s-1 = 1.4rem$font-size-s-2 = 1.6rem$font-size-s-3 = 1.8rem$font-size-m-1 = 2.2rem$font-size-m-2 = 2.8rem$font-size-m-3 = 3.4rem$font-size-m-4 = 4.2rem$font-size-l-1 = 5.2rem$font-size-l-2 = 6.4rem$font-size-l-3 = 7.8rem// line-height$line-height-s-1 = 2.2rem$line-height-s-2 = 2.4rem$line-height-s-3 = 2.6rem$line-height-m-1 = 3.0rem$line-height-m-2 = 3.6rem$line-height-m-3 = 4.4rem$line-height-m-4 = 5.6rem$line-height-l-1 = 6.6rem$line-height-l-2 = 8.2rem$line-height-l-3 = 10rem
再写一个stylu函数,在之后的样式中引用就好了:
text-size(n) if n == 's-1' font-size: $font-size-s-1 line-height: $line-height-s-1 else if n == 's-2' font-size: $font-size-s-2 line-height: $line-height-s-2 else if n == 's-3' font-size: $font-size-s-3 line-height: $line-height-s-3 else if n == 'm-1' font-size: $font-size-m-1 line-height: $line-height-m-1 else if n == 'm-2' font-size: $font-size-m-2 line-height: $line-height-m-2 else if n == 'm-3' font-size: $font-size-m-3 line-height: $line-height-m-3 else if n == 'm-4' font-size: $font-size-m-4 line-height: $line-height-m-4 else if n == 'l-1' font-size: $font-size-l-1 line-height: $line-height-l-1 else if n == 'l-2' font-size: $font-size-l-2 line-height: $line-height-l-2 else if n == 'l-3' font-size: $font-size-l-3 line-height: $line-height-l-3
色彩
官方提供了6种可配置的颜色,都是大自然的颜色!灵感来源就是大自然啊。如果你一个都不喜欢,那就自己去代码里面改了。
# theme colormain_color: default # forest | grass | sky | sun | sea
对于自己设计的评价(tu cao)
hexo主题的开发
其实设计才是最难的部分,编码反而简单,只是有点“烦人”。因为开发hexo的主题,用的是以前的前端开发方式——写模版。对我来说,还是第一次写这种模版,而我已经习惯了基于vue、react的单页面开发模式。所以会感觉比较“烦人”。不过其中有很多思想也是相似的,例如,组件化的思想。
hexo主题的开发,网络上已经有很多相关的文章,我也没必要再说一遍。这里推荐一篇博客,几乎涵盖了所有开发相关的问题。
darkmode
开启的地方在这:
这里算是一个小彩蛋,具体的实现方式可以到代码里面去看,就几十行。
不过可以告知的是,我并没有修改页面中元素自身的css。例如字体颜色color
,我并没有去修改这个属性。而是给全局加了个“滤镜”。
我个人认为这个方法还是比较巧妙的。其实就是一种偷懒的行为,因为我不想再去单独的针对darkmode设计多套配色。加一层“滤镜”可以在偷懒和效果之间达到一种比较好的平衡。效果肯定是比不上针对darkmode去重新搭配颜色的,但是也还过得去,不是吗。
其实关于编码部分,真的没有太多的感悟,就是照着自己的设计去一点点实现。
最后,如果你真的觉得paper不错,那还是欢迎使用,再过分一点还可以点赞。但是我要提醒你,它真的不太好“用”😂,后面有时间再优化吧。具体使用相关的信息可以在github上找到。
这篇文章除了介绍自己好不容易完成的主题以外,还是想告诉大家,下次找不到自己觉得好看的主题,不如试试自己的写一个。过程是挺烦的,但是最后完成了还是开心的。不管它有没有人用,别人觉得怎么样,自己看到都会很有成就感。毕竟就算是只有你一个人用你自己的主题,那你的博客主题就是独一无二的啊。
]]>原文链接:https://tympanus.net/codrops/2019/10/23/making-gooey-image-hover-effects-with-three-js/
学习如何使用噪声在着色器中创建粘稠的悬停效果。
作为Flash的替代者WebGL在近几年随着像Three.js, PIXI.js, OGL.js这样的库而变得越来越火。它们对于创建空白板非常有用,唯一的限制只有你的想象力。我们看到越来越多的WebGL创建的效果微妙地集成到交互界面中,以进行悬停,滚动或显示效果。比如 Hello Monday 或者是 cobosrl.co.
在本教程中,我们将使用Three.js创建特殊的粘稠纹理,将其用于在悬停时显示另一幅图像。你现在就可以点击演示链接,去看看真实的效果!对于演示本身,我创建了一个更实际的示例,该示例显示了带有图像的水平可滚动布局,其中每个图像都有不同的效果。你可以单击图像,它将变换为更大的版本,同时显示一些其他内容(Mock出的内容)。我们将会带你了解这个效果最有趣的部分,这样你就可以知道它是如何工作的,并且可以自己创建更多的效果!
我假设你对Javascript, Three.js以及着色器有一定的了解。如果你不了解,那么你可以先看看 Three.js documentation, The Book of Shaders, Three.js Fundamentals 或者 Discover Three.js.
注意:本教程涵盖了许多部分。如果愿意,可以跳过HTML / CSS / JavaScript部分,直接转到着色器部分。
在我们创建有趣的东西之前,需要在HTML中插入图片。在HTML / CSS中设置初始位置和尺寸,比在JavaScript中定位所有内容更容易处理场景大小。此外,样式部分应该只在CSS中定义,而不要在Javascript中。例如,如果我们的图片在桌面端的比例为16:9,而在移动设备上的比例为4:3,我们只应该使用CSS来处理。 JavaScript将仅用于请求更新数据。
// index.html<section class="container"><article class="tile"><figure class="tile__figure"><img data-src="path/to/my/image.jpg" data-hover="path/to/my/hover-image.jpg" class="tile__image" alt="My image" width="400" height="300" /></figure></article></section><canvas id="stage"></canvas>
// style.css.container {display: flex;align-items: center;justify-content: center;width: 100%;height: 100vh;z-index: 10;}.tile {width: 35vw;flex: 0 0 auto;}.tile__image {width: 100%;height: 100%;object-fit: cover;object-position: center;}canvas {position: fixed;left: 0;top: 0;width: 100%;height: 100vh;z-index: 9;}
正如你在上面看到的,我们已经创建了一个位于在屏幕居中的图像。稍后我们将利用data-src和data-hover属性,通过延迟加载在脚本中加载这两个图像。
让我们从不那么容易但也不算难的部分开始吧!首先,我们将创建场景,灯光和渲染器。
// Scene.jsimport * as THREE from 'three'export default class Scene {constructor() {this.container = document.getElementById('stage')this.scene = new THREE.Scene()this.renderer = new THREE.WebGLRenderer({canvas: this.container,alpha: true, })this.renderer.setSize(window.innerWidth, window.innerHeight)this.renderer.setPixelRatio(window.devicePixelRatio)this.initLights()}initLights() {const ambientlight = new THREE.AmbientLight(0xffffff, 2)this.scene.add(ambientlight)}}
这是一个非常基本的场景。但是我们在场景中还需要一个基本的元素:相机。我们有两种可以供选择的相机:正射或透视。如果我们想让图片保持形状不变,我们可以选择第一种。但是对于旋转效果,我们希望在移动鼠标时具有一定的透视效果。
在带有透视相机的Three.js(或者其他用于WebGL的库)中,屏幕上的10个单位值并不等于10px。因此,这里的技巧是使用一些数学运算将1单位转换为1px,并更改视角以增加或减少失真效果。
// Scene.jsconst perspective = 800constructor() {// ...this.initCamera()}initCamera() {const fov = (180 * (2 * Math.atan(window.innerHeight / 2 / perspective))) / Math.PIthis.camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, 1, 1000)this.camera.position.set(0, 0, perspective)}
我们将透视值设置为800,以便在旋转平面时不会产生太大的变形。我们增加的视角越大,我们对扭曲的感知就越少,反之亦然。然后,我们需要做的最后一件事是在每一帧中渲染场景。
// Scene.jsconstructor() {// ...this.update()}update() {requestAnimationFrame(this.update.bind(this))this.renderer.render(this.scene, this.camera)}
如果你的屏幕不是黑色的,则说明方法正确!
如上所述,我们必须从DOM中的图像上检索一些其他信息,例如其尺寸和在页面上的位置。
// Scene.jsimport Figure from './Figure'constructor() {// ...this.figure = new Figure(this.scene)}
// Figure.jsexport default class Figure {constructor(scene) {this.$image = document.querySelector('.tile__image')this.scene = scenethis.loader = new THREE.TextureLoader()this.image = this.loader.load(this.$image.dataset.src)this.hoverImage = this.loader.load(this.$image.dataset.hover)this.sizes = new THREE.Vector2(0, 0)this.offset = new THREE.Vector2(0, 0)this.getSizes()this.createMesh()}}
首先,我们创建另一个类,将场景作为属性传递给该类。我们设置了两个新的矢量,尺寸和偏移,用于存储DOM图像的尺寸和位置。
此外,我们将使用TextureLoader来“加载”图像并将其转换为纹理。我们需要这样做,因为我们想在着色器中使用这些图片。
我们需要在类中创建一个方法来处理图像的加载并等待回调。我们可以使用异步功能来实现这一目标,但对于本教程而言,我们将其保持简单。请记住,您可能需要出于自身目的对它进行一些重构。
// Figure.js// ...getSizes() {const { width, height, top, left } = this.$image.getBoundingClientRect()this.sizes.set(width, height)this.offset.set(left - window.innerWidth / 2 + width / 2, -(top - window.innerHeight / 2 + height / 2))}// ...
我们在getBoundingClientRect对象中获取图像信息。然后,将它们传递给两个变量。这里的偏移量用于计算屏幕中心与页面上的对象之间的距离。(译者:可以补充解释)
// Figure.js// ...createMesh() {this.geometry = new THREE.PlaneBufferGeometry(1, 1, 1, 1)this.material = new THREE.MeshBasicMaterial({map: this.image})this.mesh = new THREE.Mesh(this.geometry, this.material)this.mesh.position.set(this.offset.x, this.offset.y, 0)this.mesh.scale.set(this.sizes.x, this.sizes.y, 1)this.scene.add(this.mesh)}// ...
之后,我们将在平面上设置值。如您所见,我们在1px上创建了一个平面,该平面上有1行1列。由于我们不想使平面变形,所以我们不需要很多面或顶点。因此,让我们保持简单。
既然我们可以直接设置网格的大小,为什么要用缩放的方式来实现?
其实这么做主要是为了更加便于调整网格的大小。如果我们之后要更改网格的大小,除了用scale没有什么更好的方法。虽然更改网格的比例更容易直接实现,但是用来调整尺寸并不太方便。(译者:作者这里其实是一个很巧妙的做法:直接将原来的大小设置为1x1,然后采用缩放API来让网格变换为实际大小,这样缩放的比例也就等于实际的长宽值)
目前,我们设置了MeshBasicMaterial,看来一切正常。
现在,我们已经使用网格构建了场景,我们想要获取鼠标坐标,并且为了使事情变得简单,我们将其归一化。为什么要归一化?看看着色器的坐标系统你就明白了。
如上图所示,我们已经将两个着色器的值标准化了。为简单起见,我们将转化鼠标坐标以匹配顶点着色器坐标。
如果你在这里觉得理解有困难, 我建议你去看一看 Book of Shaders 和 Three.js Fundamentals的各个章节。 两者都有很好的建议,并提供了许多示例来帮助你理解。
// Figure.js// ...this.mouse = new THREE.Vector2(0, 0)window.addEventListener('mousemove', (ev) => { this.onMouseMove(ev) })// ...onMouseMove(event) {TweenMax.to(this.mouse, 0.5, {x: (event.clientX / window.innerWidth) * 2 - 1,y: -(event.clientY / window.innerHeight) * 2 + 1,})TweenMax.to(this.mesh.rotation, 0.5, {x: -this.mouse.y * 0.3,y: this.mouse.x * (Math.PI / 6)})}
对于补间部分,我将使用GreenSock的TweenMax。这是有史以来最好的库。而且非常适合我们想要达到的目的。我们不需要处理两个状态之间的转换,TweenMax会为我们完成。每次移动鼠标时,TweenMax都会平滑更新位置坐标和旋转角度。
在进行后面的步骤之前还有一件事:我们将材质从MeshBasicMaterial更新为ShaderMaterial,并传递一些值(均匀值)和着色器代码。
// Figure.js// ...this.uniforms = {u_image: { type: 't', value: this.image },u_imagehover: { type: 't', value: this.hover },u_mouse: { value: this.mouse },u_time: { value: 0 },u_res: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }}this.material = new THREE.ShaderMaterial({uniforms: this.uniforms,vertexShader: vertexShader,fragmentShader: fragmentShader})update() {this.uniforms.u_time.value += 0.01}
我们传递了两个纹理,以及鼠标的位置,屏幕的大小和一个名为u_time
的变量,该变量将在每一帧进行递增。
但是请记住,这不是最好的方法。我们只需要当我们将鼠标悬停在图形上时增加,而不必在每一帧上增加。出于性能,最好仅在需要时更新着色器。
我不会解释什么是噪声以及噪声的来源。如果你有兴趣,请探究《 The Shader of Shaders》中的相关章节,它进行了很好的解释。
长话短说,噪声是一个函数,它根据传递的值为我们提供介于-1和1之间的值。它将输出随机但却又相关的值。
多亏了噪声,我们才能生成许多不同的形状,例如地图,随机图案等。
让我们从2D噪声开始。仅通过传递纹理的坐标,我们就可以得到类似云的纹理。
但事实上有好几种噪声函数。我们使用3D噪声,再给一个参数,例如…时间?噪声图形将随着时间的流逝而变化。通过更改频率和幅度,我们可以进行一些变化并增加对比度。
其次,我们将创建一个圆。在片段着色器中构建像圆形这样的简单形状非常容易。我们只是采用了《 The Shader of Shaders:Shapes》中的功能来创建一个模糊的圆,增加对比度和视觉效果!
最后,我们将这两个加在一起,使用一些变量,让它对纹理进行“切片”:
这个混合之后的结果是不是很让人兴奋,让我们深入到代码层面继续探究!
我们这里确实不需要顶点着色器,这是我们的代码:
// vertexShader.glslvarying vec2 v_uv;void main() {v_uv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);}
Three.js的ShaderMaterial提供了一些有用的默认变量,便于初学者使用:
在这里,我们只是将UV坐标从顶点着色器传递到片段着色器。
让我们使用 The Book of Shaders中的函数来构建圆并添加一个变量来控制边缘的模糊性。
此外,我们将用鼠标位置来同步圆心坐标。这样,只要我们将鼠标移到图像上,圆就会跟随鼠标移动。
// fragmentShader.glsluniform vec2 u_mouse;uniform vec2 u_res;float circle(in vec2 _st, in float _radius, in float blurriness){vec2 dist = _st;return 1.-smoothstep(_radius-(_radius*blurriness), _radius+(_radius*blurriness), dot(dist,dist)*4.0);}void main() {vec2 st = gl_FragCoord.xy / u_res.xy - vec2(1.);// tip: use the following formula to keep the good ratio of your coordinatesst.y *= u_res.y / u_res.x;vec2 mouse = u_mouse;// tip2: do the same for your mousemouse.y *= u_res.y / u_res.x;mouse *= -1.;vec2 circlePos = st + mouse;float c = circle(circlePos, .03, 2.);gl_FragColor = vec4(vec3(c), 1.);}
正如我们在上面看到的,噪声函数具有多个参数,并为我们生成了逼真的云图案。那么我们是如何得到的呢?
对于这一部分,我将使用glslify和glsl-noise,以及两个npm包来包含其他功能。它使我们的着色器更具可读性,并且隐藏了很多我们根本不会使用的显示函数。
// fragmentShader.glsl#pragma glslify: snoise2 = require('glsl-noise/simplex/2d')//...varying vec2 v_uv;uniform float u_time;void main() {// ...float n = snoise2(vec2(v_uv.x, v_uv.y));gl_FragColor = vec4(vec3(n), 1.);}
通过更改噪声的幅度和频率(比如于sin / cos函数),我们可以更改渲染。
// fragmentShader.glslfloat offx = v_uv.x + sin(v_uv.y + u_time * .1);float offy = v_uv.y - u_time * 0.1 - cos(u_time * .001) * .01;float n = snoise2(vec2(offx, offy) * 5.) * 1.;
但这并时间的函数!它失真了,我们想要出色的效果。因此,我们将改为使用noise3d并传递第三个参数:时间。
float n = snoise3(vec3(offx, offy, u_time * .1) * 4.) * .5;
只要将它们叠加在一起,我们就可以看到随时间变化的有趣的形状。
为了解释其背后的原理,让我们假设噪声就像是在-1和1之间浮动的值。但是我们的屏幕无法显示负值或大于1(纯白色)的像素,因此我们只能看到0到1之间的值。
我们的圆形则像这样:
相加之后的近似结果:
我们非常白的像素是可见光谱之外的像素。
如果我们减小噪声并减去少量噪声,它将逐渐沿波浪向下移动,直到其消失在可见颜色的范围之内。
float n = snoise(vec3(offx, offy, u_time * .1) * 4.) - 1.;
我们的圆形仍然存在,只是可见度比较低。如果我们增加乘以它的值,它将形成更大的对比。
float c = circle(circlePos, 0.3, 0.3) * 2.5;
我们就实现我们最想要的效果了!但是正如你看到的,仍然缺少一些细节。而且我们的边缘一点也不锐利。
为了解决这个问题,我们将使用 built-in smoothstep function。
float finalMask = smoothstep(0.4, 0.5, n + c);gl_FragColor = vec4(vec3(finalMask), 1.);
借助此功能,我们将在0.4到0.5之间切出一部分图案。这些值之间的间隔越短,边缘越锐利。
最后,我们可以将混合两个纹理用作遮罩。
uniform sampler2D u_image;uniform sampler2D u_imagehover;// ...vec4 image = texture2D(u_image, uv);vec4 hover = texture2D(u_imagehover, uv);vec4 finalImage = mix(image, hover, finalMask);gl_FragColor = finalImage;
我们可以更改一些变量以产生更强的粘稠效果:
// ...float c = circle(circlePos, 0.3, 2.) * 2.5;float n = snoise3(vec3(offx, offy, u_time * .1) * 8.) - 1.;float finalMask = smoothstep(0.4, 0.5, n + pow(c, 2.));// ...
在这里可以找到完整的源码
很高兴你能读到这。这篇教程并不完美,我可能忽略了一些细节,但是我希望你仍然喜欢本教程。基于此,你可以尽情的使用更多变量,尝试其他噪声函数,并尝试使用鼠标方向或滚动发挥你的想象力来实现其他效果!
偶然间翻出一个之前收藏的网站,被开始的这个landing效果惊到了。我为什么会收藏这个网站?!我对他的业务似乎是没有丝毫兴趣,那我必定是被这炫酷(炫酷?)的视觉效果吸引了。
和通常看到的视差滚动(滑动相同距离相同,但不同的元素移动不同的距离)效果不同。当你用鼠标滚动时,它给人一种从桌面上到移动到桌面下的视觉效果。对我来说,应该是讲是第一次看到这种效果的视差滚动。
刚开始我猜测应该是在canvas
中实现的,怀着我的猜想,我自信的按下了option+command+I
!当打开console的一瞬间我惊了,不是canvas
,完全没有,竟然是用div叠加而成的!
怀着好奇之心,我决定扒一扒背后的原理。
总共6个不同的部分,分别为一个div
,总的可以分为两个部分
随着鼠标的滑动
情况1.可以观察到css属性scaleY(val)
中的val值在不断的变化,从正到负。
可以理解为随着滑动,将桌面对应的div进行Y方向的形变,正 -> 0 -> 负数,最终表现出的效果就是从桌面上移动到桌面下观察的效果。
情况2.可以观察到css属性translate3d(x,y,z)
中的y值在不断的改变。但是每一个div的变化量是不一样的,越靠近屏幕的div变化量越大,越远则越小。可以参考prespective这个概念,这样在滑动的时候就表现出视差的效果。
总结:
桌面进行伸缩变换,桌面上的物体进行平移变换。两个效果叠加出3D效果的视差滚动。
在知道了基本原理之后,发现其实并不难。想要做出这样的效果需要满足几个条件:
设计师需要事先设计出具有类似3D效果的图形
导出的资源(这里指png图片)需要合理的分块,准切的来将是在Z轴上不同深度的图形需要分别导出
其中每一步都影响着最终呈现出的效果。
这篇文章只是一个初步的原理分析,并没有提到如何去在代码中确定分块的伸缩量以及平移速度等等。这些需要进一步的去研究思考。暂时还没有去推导不同分块之间的速度关系。
其实最最重要的是想法,仔细看来这原理也不难,主要只用到了两个css transform属性。但是让人觉得很巧妙,而且效果很好。很佩服设计出这种效果的设计师和实现这种效果的前端工程师。
参考
https://imweb.io/topic/56e804ef1a5f05dc50643106
https://louiszhai.github.io/2016/06/13/regexp
https://www.cnblogs.com/shunyao8210/archive/2008/11/13/1332591.html
最近在准备面试,顺手比较系统的回顾并学习了“正则表达式”
这里是笔记pdf
不过需要注意的几点是:
应用场景:filter CSS属性将模糊或颜色偏移等图形效果应用于元素。滤镜通常用于调整图像,背景和边框的渲染。
作用对象:作用于使用该属性的元素本身
具体应用实例:DEMO
应用场景:mix-blend-mode CSS 属性描述了元素本身应该与元素的背景(元素后面的任何内容)如何混合。
作用对象:作用于使用该属性的元素本身
具体应用实例:DEMO
应用场景:background-blend-mode CSS属性定义该元素的背景(背景图片、背景色等等)之间如何混合。通常结合background-image
属性使用。
作用对象:作用于使用该属性的元素的背景
具体应用实例:DEMO
filter
属性)mix-blend-mode
和background-blend-mode
更是非常的相似,就连系统可见值
几乎都是一样的。filter
和mix-blend-mode | background-blend-mode
的系统可见值
不同filter
有:blur | brightness | contrast | drop-shadow | grayscale | hue-rotate | invert | opacity | saturate | sepiamix-blend-mode | background-blend-mode
有:normal | multiply | screen | overlay | darken | lighten | color-dodge | color-burn | hard-light | soft-light | difference | exclusion | hue | saturation | color | luminosity;filter
:
可以类比为你带着各种各样“有色眼镜”,在这种情况下再去观看被添加filter
属性的元素。mix-blend-mode
:
可以理解为,在一个像素上同时显示来自两个元素的像素,那么该怎么显示呢?显示什么颜色呢?这就要取决于你的混合模式了。
而这两个元素则分别是被设置了mix-blend-mode
属性的元素及位于其之后的元素。background-blend-mode
:可以理解为,其他地方和mix-blend-mode
类似,但是混合的对象不是来自不同的元素,而是来自同一个元素的不同背景层。设置该属性,不会影响元素背景之上的元素。
]]>
虚拟像素,可以理解为“直觉”像素,
CSS
和JS
使用的抽象单位,浏览器内的一切长度都是以CSS
像素为单位的,CSS
像素的单位是px.
设备像素(物理像素)顾名思义,显示屏是由一个个物理像素点组成的,通过控制每个像素点的颜色,使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了,单位pt.
设备独立像素,也称为逻辑像素,简称
dip
CSS像素 = 设备独立像素 = 逻辑像素
设备像素比dpr 描述的是未缩放状态下,
物理像素
和CSS像素
的初始比例关系dpr = 设备像素 / css像素
dpr = 屏幕横向设备像素 / 理想视口的宽
参考像素即为从一臂之遥看解析度为
96DPI
的设备输出(即1英寸96点)时,1点(即1/96英寸)的视角。它并不是1/96英寸长度,而是从一臂之遥的距离处看解析度为96DPI
的设备输出一单位(即1/96英寸)时视线与水平线的夹角.
通常认为常人臂长为28英寸,所以它的视角是:
(1/96)in / (28in * 2 * PI / 360deg) = 0.0213度
图片来源:https://github.com/jawil/blog/issues/21
每英寸像素取值,更确切的说法应该是像素密度,也就是衡量单位物理面积内拥有像素值的情况.
dpi:每英寸多少点
CSS像素、物理像素、逻辑像素、设备像素比、PPI、Viewport
MDN/window.devicePixelRatio
Dome在这
由于兼容性的问题还没解决,建议Chrome打开
某天在观看TED演讲High-tech art (with a sense of humor)的时候看到里面的一个项目叫做pygmies,中文名叫俾格米人(图中的小黑人)。他们都很胆小,只要有声音就会被吓到躲到板子后面。
该项目的实物是由来自印度的团队制作的。测试中,俾格米人表现得惟妙惟肖,就像是一群好奇而又胆小的小动物。十分可爱!
于是我想用web技术似乎也能达到同样的效果。只需要浏览器调用麦克风,获取数据,作用于svg元素(当然这是最初的简单想法)。
作图工具,Mac平台sketch
html部分
// 主要是svg代码,量比较大,请在源码中查看
css部分
// 主要是基本的定位代码,请在源码中查看
js部分
"use strict";var ctx, analyser, frequencies, getByteFrequencyDataAverage, draw;// 兼容性window.AudioContext = window.AudioContext || window.webkitAudioContext;// 获取音频上下文ctx = new window.AudioContext();// 用户获取stream当中的时间、频率信息analyser = ctx.createAnalyser();frequencies = new Uint8Array(analyser.frequencyBinCount);getByteFrequencyDataAverage = function() { // 将当前频域数据拷贝进数组 analyser.getByteFrequencyData(frequencies); // 求得频域的平均值 return ( frequencies.reduce(function(previous, current) { return previous + current; }) / analyser.frequencyBinCount );};// 返回 Promise 对象navigator.mediaDevices .getUserMedia({ audio: true }) .then(function(stream) { // window.hackForMozzila = stream; ctx .createMediaStreamSource(stream) // 连接到AnalyserNode .connect(analyser); }) .catch(function(err) { console.log(err.message); });var pygmies = [];for (let i = 0; i < 10; i++) { pygmies.push(document.getElementById(`pygmie-${i + 1}`));}// 改变小人的位置(draw = function() { var moveValue = getByteFrequencyDataAverage() * 10; if (moveValue >= 35) { moveValue = 35; } pygmies[0].style.transform = `translate(51.000000px, ${moveValue}px)`; console.log(getByteFrequencyDataAverage()); pygmies[1].style.transform = `translate(89.000000px, ${0.0 + moveValue}px)`; pygmies[2].style.transform = `translate(149.000000px, ${0.0 + moveValue}px)`; pygmies[3].style.transform = `translate(218.000000px, ${0.0 + moveValue}px)`; pygmies[4].style.transform = `translate(286.500000px, 51.000000px) rotate(90.000000deg) translate(-286.500000px, -51.000000px) translate(275.000000px, ${34.0 + moveValue}px)`; pygmies[5].style.transform = `translate(286.500000px, 152.000000px) rotate(90.000000deg) translate(-286.500000px, -152.000000px) translate(275.000000px, ${135.5 + moveValue}px)`; pygmies[6].style.transform = `translate(286.500000px, 196.5000000px) rotate(90.000000deg) translate(-286.500000px, -196.500000px) translate(275.000000px, ${179.5 + moveValue}px)`; pygmies[7].style.transform = `translate(17.500000px, 173.500000px) rotate(-90.000000deg) translate(-17.00000px, -173.500000px) translate(5.500000px, ${156.5 + moveValue}px)`; pygmies[8].style.transform = `translate(17.000000px, 106.500000px) rotate(-90.000000deg) translate(-17.00000px, -106.500000px) translate(5.500000px, ${89.5 + moveValue}px)`; pygmies[9].style.transform = `translate(17.00000px, 252.500000px) rotate(-90.000000deg) translate(-17.00000px, -252.500000px) translate(5.500000px, ${235.5 + moveValue}px)`; requestAnimationFrame(draw);})();
代码主要是做了两件事:
只不过需要不断的循环,来获取最新的信息。
]]>