创建和监听自定义事件

做前端开发的筒子们,有没有经常觉得原生的事件类型不够用?今天就来介绍一下如何创建自定义的事件类型吧。

举个例子,你有没有实现过像“长按”这样的需求?这是一个挺常见的需求,可是原生的事件类型里并没有它。以前通常的做法是用 mousedownmouseup 来模拟。同时监听这两个事件,记录两次事件触发的时间戳,得出按下时长,再决定是否执行后续动作。这个方案还不错,思路清晰,实现容易。

好,现在新的需求来了(该死的 PM!),页面上有两个需要监听长按状态的元素,怎么办?这好办,写具名函数嘛,复用一下还是很容易的。别急,PM 又来了…… 两个元素触发长按所需的时长不一样,怎么办?时长还要能方便的修改,怎么办?在心里咒骂 PM 肯定少不了,但需求还是得给他实现了。好,修修改改勉强又能用了。可是这么一看,代码写得真丑啊…… 万一待会 PM 又改需求怎么办?

有没有办法把这个需求实现得简单好看又灵活呢?当然可以!创建一个自定义事件吧骚年!

在文档对象模型(DOM)中有 createEventinitEventdispatchEvent 这么一组方法可以很方便的创建,初始化和触发一个自定义的事件。以前面提到的长按需求为例,我们首先需要创建一个事件对象。

var evt = document.createEvent('Event');

在这里我基于最基础的事件模块(Event)创建了一个对象,如果你需要对事件进行精细地控制,可以选择更具体的事件模块(UIEvents、MouseEvents、MutationEvents、HTMLEvents 等)。

接下来就是初始化这个事件,三个参数分别是事件类型(字符串)、是否冒泡(布尔)、是否可取消(布尔)。

evt.initEvent('longpress', true, true);

注意这里初始化事件的方法名跟上一步所选的事件模块是有关的,具体可以参照 MDN 这个表格

然后我们要做的事,就是在特定条件(按下时间超过 1 秒)下触发这个事件。

var timer;
button.addEventListener('mousedown', function() {
    timer = Date.now();
}, false);
button.addEventListener('mouseup', function() {
    if(Date.now() - timer > 1000) {
        evt.duration = Date.now() - timer;
        button.dispatchEvent(evt);
    }
}, false);

检测鼠标按下和松开之间的时间间隔,大于 1 秒(1000 毫秒)则触发自定义的 longpress 事件。在这个过程中我们还可以为自定义的事件对象增加属性,比如上面例子中的 duration 属性,即鼠标按下的时长。

最后只需要像监听原生的事件那样,监听这个自定义的事件,太简单了。

button.addEventListener('longpress', function(e) {
    console.log('Pressed for ' + e.duration + ' milliseconds.');
}, false);

到这里就结束了吗?当然没有,别忘了 PM 的需求,触发事件的时长要可以方便的修改,还要能支持多个实例。

不过我想你应该也知道要怎么做了,就是把自定义事件的这部分代码包装一下。

function enableLongPress(target, threshold) {
    var timer;
    var evt = document.createEvent('Event');
    evt.initEvent('longpress', true, true);
    target.addEventListener('mousedown', function() {
        timer = Date.now();
    }, false);
    target.addEventListener('mouseup', function() {
        if(Date.now() - timer > threshold) {
            evt.duration = Date.now() - timer;
            target.dispatchEvent(evt);
        }
    }, false);
}

把需要监听的 DOM 元素和触发事件的条件作为参数传入,调用这个函数,就完成了事件的自定义。

enableLongPress(button, 1000);

事件的监听部分完全不用改动。

button.addEventListener('longpress', function(e) {
    console.log('Pressed for ' + e.duration + ' milliseconds.');
}, false);

增加实例易如反掌。

enableLongPress(anotherButton, 2000);
anotherButton.addEventListener('longpress', function(e) {
    console.log('Pressed for ' + e.duration + ' milliseconds.');
}, false);

最后给个演示,大家随便玩。

如果您觉得这篇文章对您有帮助,可以通过 支付宝收款主页 向我捐助,帮助我早日用上 Macbook Pro,谢谢!

绝对定位的瀑布流

写了个瀑布流这种事,我会随便到处跟人讲吗?

绝对定位,页面宽度自适应,demo 在 这里。IE6/7 的用户就别看了,我敢保证,你啥都看不到的。IE8/9 没有动画效果。

似乎还发现一个 computedStyle 的奇妙 bug,有时间详细写一下……

如果您觉得这篇文章对您有帮助,可以通过 支付宝收款主页 向我捐助,帮助我早日用上 Macbook Pro,谢谢!

几个经典排序算法的 JavaScript 实现

作为一个学化学转行做互联网的人,缺乏计算机科学的基础训练一直是我的硬伤。上星期花时间专门看了一些排序类算法,并且决定要写几个实现出来,免得一转眼又忘了。经过周末休息日的死磕,我终于在理解算法的基础上,用 JavaScript 写出了归并排序和快速排序的实现。在此记录一笔,以示备忘……

不过在开始之前,还是先来看看我长久以来唯一会写的冒泡排序吧……

为方便起见,我扩展了一个交换数组项的 swap 方法。

if(!Array.prototype.swap) {
    Array.prototype.swap = function(a, b) {
        var temp = this[a];
        this[a] = this[b];
        this[b] = temp;
        return this;
    };
}

当然我们也可以用更好的方式来扩展原型,这是题外话了。

if(!Array.prototype.swap) {
    Object.defineProperty(Array.prototype, 'swap', {
        value: function(a, b) {
            var temp = this[a];
            this[a] = this[b];
            this[b] = temp;
            return this;
        }
    });
}

接下来就是真正的冒泡排序了。

var bubbleSort = function(arr) {
    var len = arr.length;
    if(len > 1) {
        for(var i = 1; i < len; i++) {
            for(var j = i; j > 0; j--) {
                if(arr[j] < arr[j-1]) {
                    arr.swap(j, j-1);
                } else {
                    break;
                }
            }
        }
    }
    return arr;
};

稍作调整也可以改成数组方法的形式,或者参考上面提到的 defineProperty 的形式。

Array.prototype.bubbleSort = function() {
    var arr = this,
        len = arr.length;
    if(len > 1) {
        for(var i = 1; i < len; i++) {
            for(var j = i; j > 0; j--) {
                if(arr[j] < arr[j-1]) {
                    arr.swap(j, j-1);
                } else {
                    break;
                }
            }
        }
    }
    return arr;
};

接下来看看比冒泡更快的归并排序吧。这个算法采用了“分而治之divide and rule/conquer)”的思想。算法的实现由“分”和“治”两个部分构成,把一个大问题分割成若干个容易解决的小问题,然后递归地逐个击破,从而解决整个大问题。

var merge = function(left, right) {
    var arr = [];
    while(left.length && right.length) {
        if(left[0] < right[0]) {
            arr.push(left.shift());
        } else {
            arr.push(right.shift());
        }
    }
    return arr.concat(left, right);
};
var mergeSort = function(arr) {
    var len = arr.length;
    if(len > 1) {
        var index = Math.floor(len/2),
            left  = arr.slice(0, index),
            right = arr.slice(index);
        return merge(mergeSort(left), mergeSort(right));
    } else {
        return arr;
    }
};

由于 JavaScript 本身并未对递归进行优化,而可以用递归实现的算法,也都可以用非递归的方式实现,因此自然有人考虑用迭代来实现归并排序。

var mergeSortIteration = function(arr) {
    var len = arr.length;
    if(len > 1) {
        var work = [];
        for(var i = 0; i < len; i++) {
            work.push([arr[i]]);
        }
        work.push([]);
        for(var j = len; j > 1; j = Math.ceil(j/2)) {
            for(var k = 0, l = 0; l < j; k++, l += 2) {
                work[k] = merge(work[l], work[l+1]);
            }
            work[k] = [];
        }
        return work[0];
    } else {
        return arr;
    }
};

最后来说说快速排序。这应该是实际生产和各种算法类面试题中最常见的排序算法了吧。快排也是一个典型的“分而治之”算法,用递归来写实现非常简单,也很容易理解。

var quickSort = function(arr) {
    var len = arr.length;
    if(len > 1) {
        var pivot = arr[0],
            left  = [],
            right = [];
        for(var i = 1; i < len; i++) {
            if(arr[i] < pivot) {
                left.push(arr[i]);
            } else {
                right.push(arr[i]);
            }
        }
        return quickSort(left).concat(pivot, quickSort(right));
    } else {
        return arr;
    }
};

这个实现有一个不足,就是会占用额外的内存空间,这跟归并算法是一样的。而归并算法很稳定,极端情况下的耗时要优于快排,因此这个快排的现实意义并不大。实际生产中大量使用的快排算法,大多是在比较过程中直接对数组元素进行交换操作,避免了占用额外的空间,这就是所谓的原地(in-place)快排。

var partition = function(arr, start, end) {
    var index = start,
        pivot = arr[start];
    arr.swap(start, end);
    for(var i = start; i < end; i++) {
        if(arr[i] < pivot) {
            arr.swap(i, index);
            index++;
        }
    }
    arr.swap(index, end);
    return index;
};
var sorting = function(arr, start, end) {
    if(end - start > 1) {
        var index = partition(arr, start, end-1);
        sorting(arr, start, index);
        sorting(arr, index+1, end);
    }
    return arr;
};
var quickSortInPlace = function(arr) {
    var len = arr.length;
    if(len > 1) {
        return sorting(arr, 0, len);
    } else {
        return arr;
    }
};

好,本文到此也应该结束了。虽然 JavaScript 数组已经有了用快排实现的原生 sort 方法,我还是希望本文提到这些算法的思路和实现会对你有帮助。

如果您觉得这篇文章对您有帮助,可以通过 支付宝收款主页 向我捐助,帮助我早日用上 Macbook Pro,谢谢!

为 WordPress 官方插件目录的页面添加图片横幅

WordPress 官方插件目录最近一次改版以后,有些插件页面(比如 Hello Dolly)增加了一个图片横幅,看起来效果很不错哦。
plugin-banner-preview

那怎么让自己光秃秃的插件页面也能用上好看的图片横幅呢?其实很简单,只要制作一张名为 banner-772×250 的图片,尺寸当然就是宽 772 像素,高 250 像素啦,图片格式可以是 png 或 jpg。在本地的 SVN 目录下新建一个名为 assets 的文件夹,把刚才制作好的图片放进这个文件夹,通过 SVN 提交到官方的插件仓库,稍等一会就可以看到效果啦。
plugin-banner-svn

我的 txt2img 山寨长微博插件在添加图片横幅以前是这样的:
plugin-banner-before

对比一下添加横幅以后的样子。怎么样?有没有觉得下载按钮更诱人了?
plugin-banner-after

WP MashSocial Widget 插件的作者就在 2 月 16 日添加了图片横幅。看看插件的下载数据,怎么样?效果很惊人吧!
plugin-banner-effect

一图胜千言!插件作者们,还等什么?赶紧给你的插件页面也填上图片横幅吧。

如果您觉得这篇文章对您有帮助,可以通过 支付宝收款主页 向我捐助,帮助我早日用上 Macbook Pro,谢谢!