几乎完全随机的不能再随机显示的标签云

公司最近在开发一个平台中。赫然出现了一个常见却又奇葩的设计交互细节。就是标签云。普通的标签云。飘浮3D什么的都还好。至少都做过也知道怎么做。可。UI交互说道。我们之前看过一个视频。就是让这些标签大小、颜色、位置随机。鼠标放上去有放大、离开有缩小效果就可以了。嗯。。。似乎很简单嘛。

在线预览:Demo

github

背景介绍

设计图如下所示

demo

一个前端看到图之后直接切了一整张图放了上去。没错。就是切了上面一整张图。果断被经理、UI和后端训斥了一会儿。让我过去看看。听Ta们比划了半天。这不是我恍然醒悟。这是不是就是标签云呀?Ta们不确定。UI要求的是随机摆放、随机背景和鼠标滑入滑出放大的效果。后端要求的是可以一个循环把所有的标签给输出出来。经理要求的是实现UI和后端的要求。还要兼容IE7+。

前端同事听了颇有无奈。转头看向了我。我。。。于是我接下了。然后开始了。。。

忐忑的过程

随机颜色

说起随机颜色。我就很容易想起来之前看到一直存在着的随机颜色获取代码。


'#'+('00000'+(Math.random()*0x1000000<<0).toString(16)).slice(-6); 

看着很高深。还涉及到了位操作符。可惜我不是很能看懂。看懂了也不是很会用。而且看设计图我还想要在设定范围内随机颜色。在这个基础上修改我也不会。只好采取最笨的方法。通过随机r、g、b的值来获取随机范围颜色。

常用web颜色格式有几种。

#012 (16位颜色简写。相当于#001122。分别对应rgb的每一位)

#000000 (16位颜色全写)
rgb(a)(0,0,0) (rgb颜色)
hsl(a)(295, 65%, 69%) (hsl颜色)
……

因为hsl使用不多。所以只支持了前三种。其实最关键的是hsl不太好用语言描述。后面说道颜色变淡加深的时候再说。
前三种格式也好区分。判断最大最小颜色值的格式。获取对应的rgb值。皆是0-255的十位数字。分别取最小r和最大r、最小g和最大g、最小b和最大b的随机值。重新组装成为新的rgb值。随机颜色完成。

随机位置和大小

这两个应该是相辅相成的。可是。要得到随机大小。我总得知道最大最小值吧。同时为了分布均匀。最小还好说。可以人为的通过感观调整。最大值呢?似乎牵扯到了数学问题。我的数学平平。只好采取一些自己能理解的方法。

假如说宽w高h的矩形。内部需要放置n个标签圆。就是n个边长相同皆为x的正方形。那么请问x怎么求得?

我开始尝试一些方法。比如w=1000,h=500,n=20

1、根据面积求得w*h/n。得到每个正方形的最大面积是25000。开方获得最大边长整数m。之后w/m取整乘以h/m取整。看结果是否大于等于20。若小于20。则m=m-1。继续与w和h相除。直至乘积结果大于等于20为止。此时的m即为最大边长n。

var x = (function(w,h,n){
    var m = ~~Math.sqrt(w*h);
    while( ~~(w/m)*~~(h/m) < 20 ){
        m--;
    }
    console.log(m);
    return m;
})(1000,500,20);

2、根据公约数求得。先获取小于w的最大非质数。再获取小于h的最大非质数。然后求得w和h的最大公约数m。之后w/m取整乘以h/m取整。看结果是否大于等于20。若小于20。则再获取w和h的次级公约数。直至乘积结果大于等于20为止。此时的m即为最大边长n。

var y = (function(w,h,n){
    function isPrime(n) { //是否为质数
        if (n <= 3) {
            return n > 1;
        }
        if (n % 2 == 0 || n % 3 == 0) {
            return false;
        }

        for (var i = 5; i * i <= n; i += 6) {
            if (n % i == 0 || n % (i + 2) == 0) {
                return false;
            }
        }
        return true;
    }
    function maxPrime(n) { //最大非质数
        var gap = 0;
        while (isPrime(n)) {
            n--;
            gap--;
        }
        return {
            num: n,
            gap: gap
        }
    }
    function maxDivisor(m, n, correct) { //最大公约数
        var u = +m,
            v = +n,
            t = n,
            _u;
        while (v != 0) {
            t = u % v;
            u = v;
            v = t;
        }
        if (correct !== undefined) {
            while (correct != 0) {
                var arr = [
                    Math.sqrt(u),
                    u / 2,
                    u / 3,
                    u / 5,
                    u / 7
                ];
                u = Math.max.apply(null, arr);
                correct--;
            }
        }
        return u;
    }
    var offset = 0, //公约数修正值
        mw = maxPrime(w),
        mh = maxPrime(h);
    var    m = maxDivisor(mw.num,mh.num,offset);
    while ( ~~(w/m)*~~(h/m) < n ) {
        m = maxDivisor(mw.num,mh.num,++offset);
    }
    console.log(m);
    return m;
})(1000,500,20);

3、共y行z列。y:z ≈ h:w。y+z>=n。然后根据千奇百怪的计算。求得最大y和z。这个只存在于我开始的想象。没有实践。

4、什么mask2维卷积、x满足参数为兰格他的泊松分布公式:P(x=k)=[兰格他^k]*[EXP(-兰格他)/k的阶乘]。什么的。我也看不懂。就不尝试了。

不怕笑话。我一开始想到的是方法一根据面积求得边长。可之后不知道如何莫名奇妙的就用了方法二。使用公约数来求。最大公约数可以实现。但是除了性能方面还有些其他问题。

公约数层级不能保证。比如w=27和h=5的公约数。就只能是1。这样哪怕是只有一个正方形n。也会被分割成27*5份。为了解决这个问题。我丧心病狂的先把w和h转为整百数来保证层级尽可能的多。虽然应该是2的N次方才是层级最多的。减掉的部分变为边距。虽然可以解决问题。但。始终不是通用之道。

最后还是修改成为了方法一。方法一中减去的数值依然变为上左边距。OK。完成。

绘制辅助正方形

既然得到了最大边长。那就可以直接使用了。不过使用之前。先使用上一步中获得的最大边长和边距来绘制一堆的正方形以此方便调试。横竖输出的时候让我想起了以前做拼图的感觉。做到这里。我忽然想起了。样式的问题。。。

样式

一般的插件引入js和css两个文件。说实话其实挺烦的。很早以前用angular的时候。ng-show和ng-hide的实现让我很意外。一看代码。原来是head上添加了一些样式。这个方法吼呀。很吼呀。虽然可能因为优先级的问题不好覆盖。但是呢。管Ta呢。。。我也要这样。把通用的样式写在js里然后放到head上。。。浏览器前缀可以直接获取写上去了。使用了window.getComputedStyle

随机位置和大小

因为有了辅助正方形的边长和位置。这一项就简单的多了。这些绘制的所有辅助正方形。每一个内部都可能会放置一个标签正方形。标签正方形最大宽度就是此辅助正方形的宽度。位置则在辅助正方形内随机。同时为了避免太贴合。不要随便的100%就好。如果在参数里有设置最大最小宽度的话。在这里判断即可。

标签背景色和文字颜色

若非为参数内的定义的颜色。则背景色取随机颜色。文字颜色就是255-r、255-g、255-b取背景色的反色。

边框颜色和文字大小

这些比较细节。再看设计图。边框颜色很显眼。而且和背景色有关联。似乎是背景色变深还是变淡的?rgb颜色变淡变深的太麻烦了。不是太麻烦。是我根本就不知道咋写。还是走简单做法吧。先把rgb转为hsl。然后降低饱和度s之后再转换回rgb。绕一圈挺麻烦。可除此之外。我也没了别的办法。

文字大小。因为标签有大小。为了整体看起来均匀。自然文字也有大小。文字大小实现的不好。就不多说了。

鼠标滑入事件

为了实现鼠标滑入放大。鼠标离开缩小。使用css3自然是最简单最方便性能也最好的方式了。可是。。。一方面还有兼容低版本ie。一方面在box边缘的那些标签放大了会溢出box。自然也不是想要的结果。思来想去。还是用jQuery的动画吧。因为要模拟中心点放大。类似于transform-origin:center center的效果。标签的宽度高度都会变大。位置的左边距和上边距也需要随之改变。还好只需要简单的计算。动画可以借助使用jQuery.fn.animate来实现。挺好。挺好。

放大。不可避免的就会遮住其他标签。如果该标签放大。其他标签缩小的话。性能影响太甚。使用js实现透明度降低动画依然性能堪忧。思来想去。还是用css3动画实现吧。鼠标滑入标签。标签放大、边框长度增加。文字大小增加、文字行高增加、其他标签透明度降低等。也就这样完成了。

最后还剩下什么?对。动画。标签出现的动画。

标签出现动画

顺手写了三个。皆需要使用js完成动画。看看timeline还是很那啥的。咳咳咳。

逐个从小变大出现。同鼠标滑入事件类似。模拟中心点放大的效果。
像东方一堆boss的非符一般。从box中心由小变大向四面八方射出。同上。
所有的标签同一动画。因为太不咋的。就当没有吧。

终于结束了

长吁一口气。做完之后发现其实也没有一开始想象的那么困难和复杂。却也不知不觉用掉了一天+的时间。现在可是正处于赶项目的阶段呀。其他同事刷页面已经超过我一二十张了。大部分是列表和详情页。作为刚到公司不到一个月的新人忽然压力很大。公司不会统计代码量吧?我讪讪的想着。回过头来看看花费这么多时间做这个。通用也不通用。炫酷也不炫酷。美妙也不美妙。只是略微实现了需求罢了。可那我干嘛不事先在css中写好50个标签的大小不一完全随机的样式。然后再随机让标签获得其中之一样式呢?既可以很快实现。又不耽误刷页面。唉。。。

也不知道值得不值得。实现一个功能总是想把功能完善的多一点儿。可做着做着一些事先想好的功能又因为各种各样的原因而搁浅。到最后想想以后可能一辈子都用不到。图什么呢?

我不知道。初听到这个需求的迷惑和完成之后心中的舒畅。应该就是意义所在了。

啧啧啧。在人多的时候最沉默。笑容也寂寞。。。