3

I'm passing text arrays to my circleCreate function, which creates a wedge for each text. What I'm trying to do is add a click event to each wedge, so when the user clicks on a wedge, it throws an alert with each wedges text.

But it's not working. Only the outer circle is alerting text. And it always says the same text. Both inner circles alert undefined.

http://jsfiddle.net/Yushell/9f7JN/

var layer = new Kinetic.Layer();

function circleCreate(vangle, vradius, vcolor, vtext) {
    startAngle = 0;
    endAngle = 0;
    for (var i = 0; i < vangle.length; i++) {
        // WEDGE
        startAngle = endAngle;
        endAngle = startAngle + vangle[i];
        var wedge = new Kinetic.Wedge({
            x: stage.getWidth() / 2,
            y: stage.getHeight() / 2,
            radius: vradius,
            angleDeg: vangle[i],
            fill: vcolor,
            stroke: 'black',
            strokeWidth: 1,
            rotationDeg: startAngle
        });
        /* CLICK NOT WORKING
        wedge.on('click', function() {
            alert(vtext[i]);
        });*/
        layer.add(wedge);
    }
    stage.add(layer);
}
4

3 回答 3

2

这是您在使用异步 JavaScript 代码(例如事件处理程序)时会遇到的典型问题。函数中的for循环circleCreate()使用一个变量i,它为每个楔形增加。这很好i用于创建楔形:

            angleDeg: vangle[i],

click但是在事件处理程序中使用它时它会失败:

            alert(vtext[i]);

这是为什么?

当您使用new Kinetic.Wedge()调用创建楔形时,这是直接在循环内完成的。此代码同步运行;它使用i循环的特定迭代运行时存在的值。

但是click事件处理程序当时没有运行。如果您从不单击,它可能根本不会运行。当你点击一个楔子时,它的事件处理程序在那个时候被调用,在原始循环完成运行很久之后。

那么,i事件处理程序运行时值是多少?这是代码最初运行时留在其中的任何值。此for循环在i等于时退出vangle.length- 换句话说,i超出了数组的末尾,因此vangle[i]is undefined

您可以使用闭包轻松解决此问题,只需为每个循环迭代调用一个函数:

var layer = new Kinetic.Layer();

function circleCreate(vangle, vradius, vcolor, vtext) {
    startAngle = 0;
    endAngle = 0;
    for (var i = 0; i < vangle.length; i++) {
        addWedge( i );
    }
    stage.add(layer);

    function addWedge( i ) {
        startAngle = endAngle;
        endAngle = startAngle + vangle[i];
        var wedge = new Kinetic.Wedge({
            x: stage.getWidth() / 2,
            y: stage.getHeight() / 2,
            radius: vradius,
            angleDeg: vangle[i],
            fill: vcolor,
            stroke: 'black',
            strokeWidth: 1,
            rotationDeg: startAngle
        });
        wedge.on('click', function() {
            alert(vtext[i]);
        });
        layer.add(wedge);
    }
}

现在发生的情况是,调用该函数会为每个循环迭代单独addWedge()捕获 的值。i如您所知,每个函数都可以有自己的局部变量/参数,并且i内部addWedge()是该函数的局部变量——特别是对该函数的每个单独调用都是局部的。(请注意,因为addWedge()它本身是一个函数,所以该函数的i内部i外部函数中的不同circleCreate()。如果这令人困惑,可以给它一个不同的名称。)

更新的小提琴

更好的方法

这就是说,我推荐一种不同的方法来构建您的数据。当我阅读您的代码时,角度和文本数组引起了我的注意:

var anglesParents = [120, 120, 120];
var parentTextArray = ['Parent1', 'Parent2', 'Parent3'];

有类似但更长的儿童和孙子数组对。

您可以将这些数组中的值与 中的vtext[i]vangle[i]引用一起使用circleCreate()

一般来说,除非有特殊原因使用这样的并行数组,否则如果将它们组合成一个对象数组,您的代码会变得更简洁:

[
    { angle: 120, text: 'Parent1' },
    { angle: 120, text: 'Parent2' },
    { angle: 120, text: 'Parent3' }
]

对于您的嵌套环,我们可以更进一步,将所有三个环组合成一个大型对象数组,描述整个嵌套环集。你有这些数组的地方:

var anglesParents = [120, 120, 120];
var anglesChildren = [120, 60, 60, 60, 60];
var anglesGrandchildren = [
    33.33, 20, 23.33, 43.33, 22.10, 25.26,
    12.63, 28, 32, 33, 27, 36, 14.4, 9.6
];
var grandchildrenTextArray = [
    'GrandCHild1', 'GrandCHild2', 'GrandCHild3', 'GrandCHild4',
    'GrandCHild5', 'GrandCHild6', 'GrandCHild7', 'GrandCHild8',
    'GrandCHild9', 'GrandCHild10', 'GrandCHild11', 'GrandCHild12',
    'GrandCHild13', 'GrandCHild14', 'GrandCHild15', 'GrandCHild16'
];
var childrenTextArray = [
    'Child1', 'Child2', 'Child3', 'Child4', 'Child5'
];
var parentTextArray = ['Parent1', 'Parent2', 'Parent3'];

这将是:

var rings = [
    {
        radius: 200,
        color: 'grey',
        slices: [
            { angle: 33.33, text: 'GrandChild1' },
            { angle: 20, text: 'GrandChild2' },
            { angle: 23.33, text: 'GrandChild3' },
            { angle: 43.33, text: 'GrandChild4' },
            { angle: 22.10, text: 'GrandChild5' },
            { angle: 25.26, text: 'GrandChild6' },
            { angle: 12.63, text: 'GrandChild7' },
            { angle: 28, text: 'GrandChild8' },
            { angle: 32, text: 'GrandChild9' },
            { angle: 33, text: 'GrandChild10' },
            { angle: 27, text: 'GrandChild10' },
            { angle: 36, text: 'GrandChild12' },
            { angle: 14.4, text: 'GrandChild13' },
            { angle: 9.6, text: 'GrandChild14' }
        ]
    },
    {
        radius: 150,
        color: 'darkgrey',
        slices: [
            { angle: 120, text: 'Child1' },
            { angle: 60, text: 'Child2' },
            { angle: 60, text: 'Child3' },
            { angle: 60, text: 'Child4' },
            { angle: 60, text: 'Child5' }
        ]
    },
    {
        radius: 100,
        color: 'lightgrey',
        slices: [
            { angle: 120, text: 'Parent1' },
            { angle: 120, text: 'Parent2' },
            { angle: 120, text: 'Parent3' }
        ]
    }
];

现在这比原来的要长,所有的angle:text:属性名称,但是这些东西用服务器和浏览器使用的 gzip 压缩非常好。

更重要的是,它有助于简化和澄清代码并避免错误。你有没有发现你的anglesGrandchildrengrandchildrenTextArray长度不一样?:-)

使用单个对象数组而不是并行数组可以防止这样的错误。

要使用此数据,请删除该circleCreate()函数以及对其的以下调用:

circleCreate(anglesGrandchildren, 200, "grey", grandchildrenTextArray);
circleCreate(anglesChildren, 150, "darkgrey", childrenTextArray);
circleCreate(anglesParents, 100, "lightgrey", parentTextArray);

并将它们替换为:

function createRings( rings ) {
    var startAngle = 0, endAngle = 0,
        x = stage.getWidth() / 2,
        y = stage.getHeight() / 2;

    rings.forEach( function( ring ) {
        ring.slices.forEach( function( slice ) {
            startAngle = endAngle;
            endAngle = startAngle + slice.angle;
            var wedge = new Kinetic.Wedge({
                x: x,
                y: y,
                radius: ring.radius,
                angleDeg: slice.angle,
                fill: ring.color,
                stroke: 'black',
                strokeWidth: 1,
                rotationDeg: startAngle
            });
            wedge.on('click', function() {
                alert(slice.text);
            });
            layer.add(wedge);
        });
    });

    stage.add(layer);
}

createRings( rings );

现在这段代码实际上并不比原始代码短,但一些细节更清晰:slice.angleslice.text清楚地表明角度和文本属于同一个切片对象,与原始代码相比vangle[i]vtext[i]我们希望vanglevtext数组是正确匹配的数组,并且彼此正确排列。

我也使用.forEach()了而不是for循环;由于您使用的是 Canvas,我们知道您使用的是现代浏览器。一件好事是forEach()使用函数调用,因此它会自动为您提供一个闭包。

此外,我将计算移到循环之外xy因为它们对于每个楔形都是相同的。

这是这个更新的代码和数据的最新小提琴。

于 2013-06-13T16:59:19.230 回答
2

因为您定义为每个循环迭代的事件处理程序的每个匿名函数都将共享相同的范围,每个函数将引用相同的 var (i) 作为您尝试显示的文本的数组地址。因为您正在使用每个循环重新定义 var i,所以您将始终看到消息数组中的最后一条文本消息显示在每个点击事件上,因为分配给 i 的最后一个值将是您的数组的长度。

这是解决方案:

var layer = new Kinetic.Layer();

function circleCreate(vangle, vradius, vcolor, vtext) {
    startAngle = 0;
    endAngle = 0;
    for (var i = 0; i < vangle.length; i++) {
        // WEDGE
        startAngle = endAngle;
        endAngle = startAngle + vangle[i];
        var wedge = new Kinetic.Wedge({
            x: stage.getWidth() / 2,
            y: stage.getHeight() / 2,
            radius: vradius,
            angleDeg: vangle[i],
            fill: vcolor,
            stroke: 'black',
            strokeWidth: 1,
            rotationDeg: startAngle
        });
        (function(index) {
            wedge.on('click', function() {
                alert(vtext[i]);
            });
        })(i)
        layer.add(wedge);
    }
    stage.add(layer);
}
于 2013-06-13T17:00:52.333 回答
1

你的问题是你的循环索引。尝试这个:

    (function(j) {
        wedge.on('click', function() {
            alert(vtext[j]);
        });
    })(i);

这里

问题是,当您的click处理程序被调用时,i它的值与循环结束时的值相同,因此vtext[i]显然是未定义的。通过将其包装在一个闭包中,您可以在为您的处理程序运行循环时保存循环索引的值。click

于 2013-06-13T16:59:18.013 回答