要想做好动效,你得先知道这些

2019 年 11 月 24 日

要想做好动效,你得先知道这些

前言


动效是用户体验很重要的一部分。随着设备性能提升和人们对于用户体验越来越高的追求,动效将会越来越重要。动效要遵循客观物理规律以及人的视觉经验,符合用户预期,让用户感觉自然,才能获得用户的喜爱。


本文将从为什么探究缓动曲线、利用物理公式探究缓动曲线、常用缓动曲线、使用曲线拟合尝试剖析苹果 ScrollView 动效参数、使用线性插值高仿 APP 动效进行介绍。希望阅读后,本文能给你在制作动效时带来一点帮助。


一、为什么探究缓动曲线


动画是源自现实世界的,人类早已习惯了一个变速运动的物理环境,一个简单的匀速动画会让人相对感觉不适。所以需要让我们的动效符合物理规律。缓动曲线表述动画变化的程度与时间的关系,常用于模拟物理世界中一些常见动作。而从动画体验来说,不同的缓动曲线会带给用户不同体验。一般为:匀速运动 < 变速运动 < 物理缓动。


苹果官方的 UIView 提供了 Linear,EaseIn,EaseOut,EaseInout 还有 bezier 动画函数,然而只是局限于使用,知其然而不知其所以然。例如用 ease-in 来做小球从高处掉下的效果,这个加速效果没有遵循相关物理原理,使得出来的动画效果不太自然。


二、利用物理公式探究缓动曲线


以下以弹簧动画为例,探究一下怎样模拟出这个效果。


iOS 9 提供了 CASpringAnimation 类实现该效果,而 Web 上就没有提供类似函数。但我们仍然可以通过以前学过的物理学和数学知识来做一下研究。


下面有一个弹簧块,假设它质量为 1,在它不动的时候位置是 x = 1,则拉伸时的距离就是 x-1 了:



将这比作一个动画,弹簧块在时间 t 时所处的位置 x 就可以看作动画曲线函数 x = f(t)。如果我们求得这个函数公式,就可以模拟出这个动画效果了。对此,下图将通过物理学公式和数学知识进行探讨。



在 Wolfram | Alpha 中输入以上公式后得出:



使用工具绘制函数得:



感觉还是蛮像一个弹簧曲线的运动轨迹的嘛。像这样,如果我们要模仿自然生活中的某个运动轨迹,可以如上探究一下背后的物理方程,运用数学知识计算,和使用合适的工具,来模拟出对应的运动曲线。但估计很多人都把这些知识还给老师了,因此如果所有曲线都要自己探究的话,就真是太难了。


不要担心,后面还有一大半的篇幅,就是帮你解决这个问题的。


三、常用缓动曲线


下面是常见的缓动曲线(tween 算法),我们下面将给出对应曲线的函数公式和代码,cubic-bezier 这个网站还提供了对应贝塞尔参数。



EaseIn 是从慢到快的曲线,就像开车时先慢后快,EaseOut 和 EaseIn 的曲线图像关于(0.5,0.5)中心对称,EaseInOut:分别由 EaseIn、EaseOut 分别缩小一半,然后再拼接一起。


Quad,Cubic,Quart ,Quint:幂函数二次到五次曲线


// Modeled after the parabola y = x^2double fsQuadraticEaseIn(double p){return p * p;}// Modeled after the parabola y = -x^2 + 2xdouble fsQuadraticEaseOut(double p){return -(p * (p - 2));}// Modeled after the piecewise quadratic// y = (1/2)((2x)^2)             ; [0, 0.5)// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]double fsQuadraticEaseInOut(double p){if(p < 0.5){return 2 * p * p;}else{return (-2 * p * p) + (4 * p) - 1;}}

// Modeled after the cubic y = x^3double fsCubicEaseIn(double p){return p * p * p;}// Modeled after the cubic y = (x - 1)^3 + 1double fsCubicEaseOut(double p){double f = (p - 1);return f * f * f + 1;}// Modeled after the piecewise cubic// y = (1/2)((2x)^3) ; [0, 0.5)// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]double fsCubicEaseInOut(double p){if(p < 0.5){return 4 * p * p * p;}else{double f = ((2 * p) - 2);return 0.5 * f * f * f + 1;}}

// Modeled after the quartic x^4double fsQuarticEaseIn(double p){return p * p * p * p;}// Modeled after the quartic y = 1 - (x - 1)^4double fsQuarticEaseOut(double p){double f = (p - 1);return f * f * f * (1 - p) + 1;}// Modeled after the piecewise quartic// y = (1/2)((2x)^4) ; [0, 0.5)// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]double fsQuarticEaseInOut(double p){if(p < 0.5){return 8 * p * p * p * p;}else{double f = (p - 1);return -8 * f * f * f * f + 1;}}// Modeled after the quintic y = x^5double fsQuinticEaseIn(double p){return p * p * p * p * p;}// Modeled after the quintic y = (x - 1)^5 + 1double fsQuinticEaseOut(double p){double f = (p - 1);return f * f * f * f * f + 1;}// Modeled after the piecewise quintic// y = (1/2)((2x)^5) ; [0, 0.5)// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]double fsQuinticEaseInOut(double p){if(p < 0.5){return 16 * p * p * p * p * p;}else{double f = ((2 * p) - 2);return 0.5 * f * f * f * f * f + 1;}}
复制代码


Sine :正弦函数曲线,常用于模拟波浪和呼吸效果


// Modeled after quarter-cycle of sine wavedouble fsSineEaseIn(double p){return sin((p - 1) * M_PI_2) + 1;}// Modeled after quarter-cycle of sine wave (different phase)double fsSineEaseOut(double p){return sin(p * M_PI_2);}// Modeled after half sine wavedouble fsSineEaseInOut(double p){return 0.5 * (1 - cos(p * M_PI));}
复制代码


Expo:2^(10(x-1)),指数函数,开始很慢后期很快


// Modeled after the exponential function y = 2^(10(x - 1))double fsExponentialEaseIn(double p){return (p == 0.0) ? p : pow(2, 10 * (p - 1));}// Modeled after the exponential function y = -2^(-10x) + 1double fsExponentialEaseOut(double p){return (p == 1.0) ? p : 1 - pow(2, -10 * p);}// Modeled after the piecewise exponential// y = (1/2)2^(10(2x - 1))         ; [0,0.5)// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]double fsExponentialEaseInOut(double p){if(p == 0.0 || p == 1.0) return p;
if(p < 0.5){return 0.5 * pow(2, (20 * p) - 10);}else{return -0.5 * pow(2, (-20 * p) + 10) + 1;}}
复制代码


Circ:顾名思义就是弧(1/4 圆,如果选择了 InOut 就是两个外切的 1/4 圆)


// Modeled after shifted quadrant IV of unit circledouble fsCircularEaseIn(double p){return 1 - sqrt(1 - (p * p));}// Modeled after shifted quadrant II of unit circle         ? ?double fsCircularEaseOut(double p){return sqrt((2 - p) * p);}// Modeled after the piecewise circular function// y = (1/2)(1 - sqrt(1 - 4x^2))           ; [0, 0.5)// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]double fsCircularEaseInOut(double p){if(p < 0.5){return 0.5 * (1 - sqrt(1 - 4 * (p * p)));}else{return 0.5 * (sqrt(-((2 * p) - 3) * ((2 * p) - 1)) + 1);}}
复制代码


Bounce:这是个模拟小球落地的反弹曲线,运动时间每次按 0.5 倍衰减


double fsBounceEaseIn(double p){return 1 - fsBounceEaseOut(1 - p);}double fsBounceEaseOut(double p){if(p < 4/11.0){return (121 * p * p)/16.0;}else if(p < 8/11.0){return (363/40.0 * p * p) - (99/10.0 * p) + 17/5.0;}else if(p < 9/10.0){return (4356/361.0 * p * p) - (35442/1805.0 * p) + 16061/1805.0;}else{return (54/5.0 * p * p) - (513/25.0 * p) + 268/25.0;}}double fsBounceEaseInOut(double p){if(p < 0.5){return 0.5 * fsBounceEaseIn(p*2);}else{return 0.5 * fsBounceEaseOut(p * 2 - 1) + 0.5;}}
复制代码


Back: 这是个模拟弹簧运动过阻尼曲线


// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)double fsBackEaseIn(double p){return p * p * p - p * sin(p * M_PI);}// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))double fsBackEaseOut(double p){double f = (1 - p);return 1 - (f * f * f - f * sin(f * M_PI));}// Modeled after the piecewise overshooting cubic function:// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi))           ; [0, 0.5)// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]double fsBackEaseInOut(double p){if(p < 0.5){double f = 2 * p;return 0.5 * (f * f * f - f * sin(f * M_PI));}else{double f = (1 - (2*p - 1));return 0.5 * (1 - (f * f * f - f * sin(f * M_PI))) + 0.5;}}
复制代码


Elastic:这是个模拟弹簧运动欠阻尼曲线,就是我们前面研究想得出的曲线,


// Modeled after the damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1))double fsElasticEaseIn(double p){return sin(13 * M_PI_2 * p) * pow(2, 10 * (p - 1));}// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1double fsElasticEaseOut(double p){return sin(-13 * M_PI_2 * (p + 1)) * pow(2, -10 * p) + 1;}// Modeled after the piecewise exponentially-damped sine wave:// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1))      ; [0,0.5)// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]double fsElasticEaseInOut(double p){if(p < 0.5){return 0.5 * sin(13 * M_PI_2 * (2 * p)) * pow(2, 10 * ((2 * p) - 1));}else{return 0.5 * (sin(-13 * M_PI_2 * ((2 * p - 1) + 1)) * pow(2, -10 * (2 * p - 1)) + 2);}}
复制代码


下面是使用 MATLAB 绘制的幂函数缓动曲线里程、速度、加速度随时间变化图,可以看到幂函数次数越高,曲线前期越平缓后期越陡峭,动效的动静对比也就越强。



LTMorphingLabel 用 Swift 编写的 UILabel 子类,实现了 iOS8 中 iMessage 文字变换动画。它用到了 EaseInQuint,EaseOutQuint,EaseOutBack,EaseOutBounce 4 个缓动曲线,下面是一些效果图,另外附上我写的 OC 版本源码 FSMorphingLabel:


使用 5 阶幂函数 EaseQuint 突出动效的动静对比:



使用 EaseOutBack 模拟悬挂掉落的过程:


使用 EaseOutBounce 模拟自然掉落后回弹:



下面的思维导图是我对 FSMorphingLabel 的一些解读,建议结合源码一起查看:



四、使用 Matlab 曲线拟合尝试剖析苹果 ScrollView 动效参数


苹果公司对用户体验做的可以说是行业典范。学习经典案例不但能让我们学到新知识,还可以少走弯路。通过代码获取 ScrollView 开始拖动减速后滑动的速度,减速过程中每个时间段的位移,最终位移数据。我们使用 matlab 来分析这些数据之间的一些关系。


初始速度(V)和滑动位移(S)之间的多项式拟合:


xdata = [7.481126 7.077155 6.764386 6.615895 6.398847 6.012318 5.336042 5.037438 4.632423 2.499686 1.901368 1.376071 0.770763 0.523422 0.507804 0.460093 ];ydata = [3732.000000 3530.000000 3374.000000 3299.500000 3191.000000 2998.000000 2660.500000 2511.000000 2309.000000 1243.500000 944.500000 682.500000 380.000000 256.500000 248.500000 225.000000 ];p = polyfit(xdata, ydata, 1)
fitxdataArr = 0:0.2:15;yFitArr = polyval(p, fitxdataArr);
plot(xdata, ydata, 'o');hold on; grid on;plot(fitxdataArr, yFitArr, 'linewidth',2 );xlabel('速度(v)');ylabel('里程(s)');legend('原始数据', '拟合曲线')
复制代码



拟合出来的结果不是猜测的匀减速二次关系,而是线性的,S = 500*V – 5; 我们可以看成是 S = 500*V;


初始速度(V)和滑动时间(T)使用 2 到 5 次的多项式拟合图:


下面是分别使用的 2 到 5 次幂函数用最小二乘法去拟合,下图红色是原始数据,蓝色是拟合后后曲线,预测明显不符合走势。



于是我们又使用了对数模型: F = x(1)*log2(xdata) + x(2);


myfun.m:


function F = myfun(x, xdata)F = x(1)*log2(xdata) + x(2);end
复制代码


fit.m:


xdata = [7.481126 7.077155 6.764386 6.615895 6.398847 6.012318 5.336042 5.037438 4.632423 2.499686 1.901368 1.376071 0.770763 0.523422 0.507804 0.460093 ];ydata = [3.296769 3.279325 3.246985 3.230296 3.229400 3.196763 3.160944 3.096877 3.051205 2.768784 2.634592 2.467705 2.184812 1.984712 1.950520 1.900448 ];
x0 = [2 0];[coefArr,resnorm] = lsqcurvefit(@myfun, x0,xdata, ydata)
fitxdataArr = 0:0.2:20;yFitArr = myfun(coefArr, fitxdataArr);plot(xdata, ydata, fitxdataArr, yFitArr, 'linewidth',1 );
复制代码



得到滑动时间 T = 0.3452*log2(V) + 2.3019,吻合得很好。


这边如果是匀减速的模型,初始速度和时间的比例就是线性的,这个体验不好,安卓的貌似就是匀减速,改天验证一下。


滑动时间(T)与里程(S)之间的关系:


类似上面的也使用了多项式进行拟合,发现不对,曲线在样本数据区间走势不一样,最后我们使用的指数函数 F= x(1)*2.^(x(2)*xdata) + x(3)作为经验公式,初始参数 X=[ 0.001 -10 0]进行模拟退火计算,得到参数 [-0.9777 -7.4895 1.0043],经过多组数据测试,发现第一个和第三个参数波动在 0.01 内可以近似成 1,而第二个参数会随着不同初始速度大小呈反函数曲线的关系,从而无法确定。不过函数每次计算均方差都在 0.001 以下,可以看到下面的拟合曲线完美遮住了原始数据,可以确定指数模型是正确的。


myfun.m:


function F = myfun(x, xdata)F = x(1)*2.^(x(2)*xdata) + x(3);end
复制代码


fit.m:


xdata = [0.000000 0.000378 0.005109 0.011560 0.018097 0.024582 0.031021 0.037514 0.043967 0.050396 0.056848 0.063343 0.069754 0.076235 0.082703 0.089150 0.095607 0.102078 0.108535 0.114984 0.121442 0.127901 0.134363 0.140835 0.147283 0.153736 0.160199 0.166660 0.173118 0.179591 0.186018 0.192399 0.198885 0.205318 0.211814 0.218280 0.224735 0.231249 0.237728 0.244167 0.250627 0.257105 0.263540 0.269978 0.276416 0.282928 0.289383 0.295862 0.302294 0.308781 0.315241 0.321681 0.328127 0.334616 0.341050 0.347515 0.353993 0.360436 0.366884 0.373369 0.379804 0.386289 0.392751 0.399191 0.405641 0.412126 0.418565 0.425037 0.431430 0.437948 0.444395 0.450879 0.457318 0.463781 0.470227 0.476694 0.483150 0.489788 0.496073 0.502528 0.509003 0.515450 0.521907 0.528384 0.534848 0.541223 0.547798 0.554215 0.560576 0.567079 0.573566 0.579977 0.586440 0.592902 0.599360 0.605827 0.612332 0.618809 0.625250 0.631715 0.638173 0.644650 0.651082 0.657577 0.663956 0.670472 0.676926 0.683405 0.689841 0.696304 0.702780 0.709226 0.715672 0.722162 0.728595 0.735059 0.741537 0.747972 0.754428 0.760873 0.767310 0.773747 0.780275 0.786729 0.793185 0.799670 0.806105 0.819003 0.825398 0.831953 0.838424 0.851283 0.857681 0.864243 0.877113 0.890031 0.896437 0.909412 0.922341 0.935256 0.948169 0.967446 0.980412 0.999779 ];ydata = [0.000000 0.033053 0.056022 0.086835 0.117087 0.146218 0.174230 0.201681 0.228011 0.253221 0.277871 0.301961 0.324930 0.347339 0.368627 0.389916 0.410084 0.429692 0.448179 0.466667 0.484034 0.501401 0.517647 0.533894 0.549020 0.564146 0.578711 0.592717 0.606162 0.619048 0.631933 0.644258 0.656022 0.667227 0.678431 0.689076 0.699720 0.709804 0.719328 0.728852 0.737815 0.746218 0.755182 0.763025 0.770868 0.778711 0.785994 0.793277 0.800560 0.806723 0.813445 0.819608 0.825770 0.831933 0.837535 0.843137 0.848179 0.853221 0.858263 0.863305 0.867787 0.872269 0.876751 0.880672 0.885154 0.889076 0.892437 0.896359 0.899720 0.903081 0.906443 0.909804 0.913165 0.915966 0.918768 0.921569 0.924370 0.927171 0.929412 0.932213 0.934454 0.936695 0.938936 0.941176 0.943417 0.945098 0.947339 0.949020 0.950700 0.952941 0.954622 0.956303 0.957423 0.959104 0.960784 0.961905 0.963585 0.964706 0.966387 0.967507 0.968627 0.969748 0.970868 0.971989 0.973109 0.974230 0.975350 0.976471 0.977031 0.978151 0.978711 0.979832 0.980392 0.981513 0.982073 0.983193 0.983754 0.984314 0.984874 0.985434 0.986555 0.987115 0.987675 0.988235 0.988796 0.989356 0.989916 0.990476 0.991036 0.991597 0.992157 0.992717 0.993277 0.993838 0.994398 0.994958 0.995518 0.996078 0.996639 0.997199 0.997759 0.998319 0.998880 0.999440 ];
x0 = [0.001 -10 0];[coefArr,resnorm] = lsqcurvefit(@myfun, x0, xdata, ydata)
fitxdataArr = 0:0.01:1;yFitArr = myfun(coefArr, fitxdataArr);subplot(1,2,1);plot(xdata, ydata, fitxdataArr, yFitArr, 'linewidth',2 );legend('原始数据', '拟合曲线');xlabel('时间(t)');ylabel('');grid on,axis equal
subplot(1,2,2);plot(xdata, ydata)legend('原始数据');xlabel('时间(t)');ylabel('');grid on,axis equal
复制代码


总结我们发现苹果 ScrollView 减速使用的不是简单的匀减速模型,而看的是更像是复杂过阻尼模型,过阻尼模型更贴近自然,日常的关门就是过阻尼运动,能避免很吵的碰撞冲击。


五、使用线性插值高仿 APP 动效


上面介绍了从物理公式推导中获取运动方程的方式、使用曲线拟合的方法获取缓动公式,下面我们还有一种更加简单的方式来做出跟效果一样的动画。


AppSotre 上有很多让人惊艳的 APP,他们的交互值得我们学习,下面我以蘑菇街为例简单分析一下如何在没有设计稿和动效参数的情况下,使用简单的线性插值来做出几乎跟原版一模一样的交互。


首先我们打开 QuickTimePlayer,点击文件下面的影片录制,然后打开对应 APP 页面进行视频录制。录制完成后使用 GIFBrewery 打开,慢动作播放对应视频,在熟悉了视频中的大部分动作后,使用 XScope 工具对关键动作的真实位置进行测量,结合 GIFBrewery 中对应的时间轴,我们就知道了一个动作的开始时间结束时间、开始位置结束位置,足够我们进行线性插值了。


一些明显使用了缓动曲线的动效,多取几个动效点,用折线段来逼近曲线,可以近似出任何动效曲线效果,近似的思想是无敌的,在实际应用中,我们无需获取到准确的函数或方程,效果一样就行。


忘记了还有一步最重要的,使用 iOS image extractor 提取 ipa 包中的所有图片,这软件就是你的专用切图师,只要有安装包,你就能拿到他们的图片。当然,随着去年 iTunes 的升级,现在已经无法从 iTunes 上面下载到 ipa 安装包了,建议从 apk 文件下手。


下面附上几个工具的 icon 截图:



使用 xScope 和 GifBrwery 进行关键帧参数测量:



插值核心代码,真的简单,难在上面的参数测量上:


float calculate(float begin, float end, float lowerBound, float upperBound, float curVal){    if (curVal<lowerBound) {        curVal = lowerBound;    }    if (curVal>upperBound) {        curVal = upperBound;    }    float t = (curVal-lowerBound) / (upperBound-lowerBound);    return begin + (end-begin)*t;;}
复制代码


下面是我仿的两个 app 的代码:


仿蘑菇街:


https://github.com/wengzf/MushroomGuide



仿天巡:


https://github.com/wengzf/SkyScanner



一件优秀的作品需要大量的时间去思考去打磨,仿佛破蛹成蝶。


作者介绍


翁志方,携程内容信息研发部“旅拍”前端开发。曾参加 ACM-ICPC 获银奖,目前喜欢研究各种新颖的交互和实现。


本文转载自公众号携程技术中心(ID:ctriptech)。


原文链接


https://mp.weixin.qq.com/s?__biz=MjM5MDI3MjA5MQ==&mid=2697269147&idx=3&sn=4f6b83b736261cd82162c486ce75f84c&chksm=8376f0afb40179b95948495730d42b78f25fbefa7163605101838b7fe2f0085cfc4071db797c&scene=27#wechat_redirect


2019 年 11 月 24 日 08:001037

评论

发布
暂无评论
发现更多内容

Git 常用操作汇总-cheat sheet

多选参数

git GitHub gitlab gitee

如何搭建一个HBase集群

Rayjun

HBase

那些让程序员目瞪口呆的Bug

Java小咖秀

程序员 程序员人生 bug

java 后端博客系统文章系统——No3

猿灯塔

一个爱不释手的Apifox,让我扔掉 Postman的想法

给你买橘子

Java 编程 程序员 开发 Postman

计算机操作系统基础(十七)---进程同步之Unix域套接字

书旅

php laravel 线程 操作系统 进程

如果你想写自己的Benchmark框架

程序那些事

JVM 性能调优 GC benchmark

猿灯塔:spring Boot Starter开发及源码刨析(三)

猿灯塔

Java 猿灯塔

DOM 树的构建

法正

html DOM 前端进阶训练营

给 Spring Boot 项目减减肥!18.18M 到 0.18M 是如何做到的?

给你买橘子

Java 程序员 Spring Cloud 编码 SpringBoot 2

ARTS 打卡 第2周

Scotty

无价值人生记录.0:浪费1000%时间去做一个用来节省1%时间的“轮子玩具”(上:因缘)

八苦-瞿昙

C# 程序员人生 随笔 随笔杂谈 aop

基于Kubernetes实现的大数据采集与存储实践总结

岿然独存5

Docker Kubernetes S3 EFK Fluentd

游戏夜读 | 如何分析游戏体验?

game1night

分布式系统的一些基础理论

俊俊哥

分布式事务 CAP Base

redis里的数据结构

流沙

redis

创业使人成长系列 (2)- 散伙协议

石云升

创业 股权 合伙人 散伙协议

图解:深度优先搜索与广度优先搜索

淡蓝色

Java 数据结构 算法

使用 Dockerfile 创建镜像 | Docker 系列

AlwaysBeta

Docker 容器 镜像 Dockerfile 容器技术

如何基于 BitMap 进行海量数据分析

GrowingIO技术专栏

互联网 数据分析 科技互联网 数据化

图说前端-使用Atomics避免SharedArrayBuffers中的race conditions(3/3)

梦见君笑

前端 内存管理 前端进阶训练营

《精益思想》读后感分享

zhongzhq

高效工作 精益 精益思想 精益生产方式

架构师必须知道的架构知识

Chank

架构 架构师 Architecture Architect

啃碎并发(九):内存模型之基础概述

猿灯塔

Java 猿灯塔

图说前端-ArrayBuffers 和 SharedArrayBuffers(2/3)

梦见君笑

前端 内存管理 前端进阶训练营

RESTful 架构及实践

pingan8787

Java 前端 RESTf

Java 线程的生老病死

武培轩

Java 线程 多线程 并发 线程状态

刘华:上云还是不上云,这是一个问题

刘华Kenneth

架构 敏捷

玩转Redis高可用 - 哨兵(Sentinel)模式

Man

高可用 redis高可用 中间件

redis系列之——Redis为什么这么快?

诸葛小猿

Java redis 程序员

图说前端-内存管理(1/3)

梦见君笑

前端 内存 前端进阶训练营

要想做好动效,你得先知道这些-InfoQ