乐趣区

篮球比分几比几——纯js实现的数字轮盘转动动效

篮球比分几比几——纯 js 实现的数字轮盘转动动效

原谅我这次标题党了哈,这其实就是一个数字的翻牌器的动画效果,只不过,我们可以自己完全去用 js 来实现而不需要用到其他东西。

先来看看效果:

(ps:1. 为了响应题目特意做了红黑的篮球比分牌的样子;2.gif 是循环的哦~~)

这种效果让我做的话,我一开始是用会四列的 <ul><li> 来实现。转动的列表嘛,最直观的就是这样了。
这种方法最麻烦就是要做两三层的包裹,然后各种设置overflow, 特别是有些时候还有恶心的滚动条出现,滚动条不仅不美观,还会让我们算出来的每个元素宽度有误差,其实不算是很好的方法。

但是这是过去的事情了。以前我没得选,现在我想 做个好人 background-position

Σ(っ°Д°;)っ what?! background-position能写这玩意?

没错,就是能写。如果想不到怎么写的朋友可以先去回忆一下雪碧图这个知识点。

(ΩДΩ)诶,雪碧图?这个吗?

不对,我们这里的雪碧图是指 Image sprites 技术,也可以称之为 CSS 贴图定位、图像精灵(sprite,意为精灵),被运用于众多使用大量小图标的网页应用之上。它可取图像的一部分来使用,使得使用一个图像文件替代多个小文件成为可能。相较于一个小图标一个图像文件,单独一张图片所需的 HTTP 请求更少,对内存和带宽更加友好。
详情可以查看 MDN 的介绍

这里的雪碧图能够实现的原因就是 css 上有 background-position 的属性,在一张图片上取不同的位置作为背景,就可以得到不同背景。

o(~▽~)d 好,这个我懂了,那它和我们今天要讲的动效有什么关系呢?

想一下~~ 如果 background-position 动起来了,会有什么样子呢?

⊙(・◇・)?会上下左右动 … 这有什么特别的吗 …

没错,只能上下左右动。但是这样动起来的话,不就可以实现我们今天的动效了吗?

如果,设计狮大大给到这样一张图片:

再结合我们刚讨论的background-position,是不是就知道要怎么做了?

(゜ - ゜)上下移动的话 …

没错,我们可以通过一帧帧向上或者向下来改变 background-position 的值来达到数字轮盘的效果!

(`⌒´メ)哼!

嗯?怎么了?

(`⌒´メ)说好的纯 js 实现呢?所以你还不是要有图才能做?如果设计狮大大不给图你还不是什么都做不了!

你这还真的说到点上了,要图的每做一款动效都要画图,这岂不是很麻烦?

别急,我们可是前端 er,图这东西只要是不复杂,我们还能自己画嘛~~

接下来,让我们掏出 canvas,画画儿~~:

const canvas = document.createElement('CANVAS'); // 首先我们先创建一个 canvas
    canvas.height = (30 + 5) * 10; // 然后设定 canvas 的长宽,这个跟我们要设定的字体有关 
    canvas.width = 40; // 我们假设需要做一个 30px 的字,距离上下左右的间距为 5px 的图
    const ctx = canvas.getContext('2d');
    ctx.font = '30px Impact'; // 设置字体大小和字体样式
    ctx.fillStyle = 'red'; // 设置字体样式
    for (let i = 0; i < 10; i++) { // 开始循环写数字
        ctx.fillText(i, 5, 30 + (i * 35));
    }

这小段代码就可以给我们画出用于做动画的简单数字图:

在这里我们设定了数字的大小为 30px,上下间距为 5px,所以相应的 canvas 的总高度为:(30 + 5) 10;因此每一个数字 i 的对应的高度就为i 35(这个数字很重要,后面会说到);

在这里圈个重点,你会发现我们代码中开始在 canvas 写文字是从坐标(5, 30)开始画起的,你可能就要问了:为什么不是从(5,0)的位置画?你从(5,30)开始画不就顶上留有 30px 的空隙了吗?
还真没有空隙。因为canvas 的文字的坐标是和一般的不同的。我们一般 canvas 或者 dom 的元素,都是以左上角的点为对齐坐标的标准点,但是 canvas 的文字不是,它是以左下角的点为标准点的;更确切的说,是以文字的 baseline 的最左端作为标准点。不信?我们在(5,30)处画个点和一条线就可以看到:

所以这里就要稍微注意一下。

做出来之后我们完全不需要加到 dom 中去,可以直接从 canvas 上导出来:

const bgimg = canvas.toDataURL();

这样我们就有了一条可以用的图了~~~

(o゚▽゚)然后呢然后呢!

然后我们就可以继续写下去了——把背景图设置到 dom 上,然后使用之前提到的 rAF 和 Tween 来编写动画:

首先是 rAF 模块,这次使用了回调函数使得 rAF 可以从函数中独立出来,达到让 rAF 和其他动画模块的解耦的效果。

/**
* rAFPart
* @param duration 动画的执行时间
* @param callback 回调函数,用来改变 dom 样式,会获得 progress 当前时间进程和 duration 动画执行作为参数,方便调用 TWEEN
*/
function rAFPart(duration, callback) {
        let startTime = 0;
        function rolllingStep(timestamp) {if (!startTime) {startTime = timestamp;}
            const progress = timestamp - startTime;
            callback(progress,duration);
            if (progress <= duration) {requestAnimationFrame(rolllingStep);
            }
        }
        requestAnimationFrame(rolllingStep);
    }

其次是 TWEEN 函数,用于计算区间插值:(使用了 quadEaseOut 的缓动函数,参数就不再重复注释啦)

 function Tween(t, b, c, d) {if ((t /= d / 2) < 1) return c / 2 * t * t + b;
        return -c / 2 * ((--t) * (t - 2) - 1) + b;
    }

然后我们就可以来写一下效果:

let cacheRed1 = 0; // 设置四个全局变量来缓存目前四个数字的 background-position,这个是红色的个位数
let cacheRed2 = 0;  // 红色 十位
let cacheBlack1= 0; // 黑色 个位
let cacheBlack2 = 0; // 黑色 十位
function rolling() {
        const cr1 = cacheRed1++;
        const br1 = cacheBlack1++; // 缓存执行滚动前的 background-position 数据
        rAFPart(1000, (progress, duration) => { // 执行个位数的翻牌滚动
            const red1BgPos = Math.ceil(Tween(progress, cr1 * 35, 35, duration).toFixed(2));
            const blk1BgPos = Math.ceil(Tween(progress, br1 * 35, 35, duration).toFixed(2));
            BlackNumber[1].style.backgroundPosition = '0 -' + blk1BgPos + 'px';
            RedNumber[1].style.backgroundPosition = '0 -' + red1BgPos + 'px';
        });
        if (cacheBlack1 >= (cacheBlack2 + 1) * 10) { // 判断黑色十位上的数是否需要翻牌
            const br2 = cacheBlack2++; // 缓存然后自增
            rAFPart(1000, (progress, duration) => { // 执行翻牌动作
                const pos = Math.ceil(Tween(progress, br2 * 35, 35, duration).toFixed(2));
                BlackNumber[0].style.backgroundPosition = '0 -' + pos + 'px';

            });
        }
        if (cacheRed1 >= (cacheRed2 + 1) * 10) { // 判断黑色十位上的数是否需要翻牌
            const cr2 = cacheRed2++;
            rAFPart(1000, (progress, duration) => {const pos = Math.ceil(Tween(progress, cr2 * 35, 35, duration).toFixed(2));
                RedNumber[0].style.backgroundPosition = '0 -' + pos + 'px';

            });
        }
        setTimeout(() => { // 重复执行
            rolling();}, 1500)

    }

这里面有两个点要解释一下,首先是我们没有详细去记具体的 background-position,而是用了 0 - 9 的数字来表示,是因为我们上面画数字图的时候就已经知道一个数字所占的高度是多少,所以每个数字在哪个位置我们都知道的清清楚楚;而且如果我们后面要跳转到指定数字的时候,用 0 - 9 显然更好设计程序来判断。然后是要注意缓存,rAF 模块不是一次就执行完的,它每次执行回来调取回调函数的时候都要用到上一次自增之前的background-position 来加入到 TWEEN 中计算,所以要先把值给缓存下来,不然会出现 bug。

好,到此为止,效果就做好啦!再次放一下效果图:

最后加料: 在浏览器调试的时候发现了 background-position 的位置如果超过了图片大小浏览器就会去自动 repeat 来延长,也就是我上面的长 350 的数字图片,如果把 background-position 的高度设置为 350px,浏览器就会显示 repeat 的图片的 0px 的位置作为背景,所以要做多圈轮转动画的朋友,知道该怎么做了吗?(斜眼笑)

最后的最后啰嗦一下: 这个效果用来动效是没问题的,因为它不需要交互。但是需要交互情况下,这个效果就估计不适用了哦~~

Homer 2019.04.21

参考

jQuery 之家的一个付费效果

不,我没有买这个效果的源码,我只是按了一下 F12,然后做了一下分析而已~~

在 CSS 中实现图像合并

本文为原创,未经允许,不得转载~~

有错漏之处,敬请指正~~

附上 github 博客上地址(上有可运行 demo):https://github.com/homerious/animationShare/blob/master/main/homer/turnningNumber/README.md

感兴趣的可以上去看看哦~~

END

退出移动版