实施Bacon.JS以查找每个用户在第三方窗口小部件上花费的平均时间的问题

时间:2014-12-24 19:27:40

标签: javascript functional-programming bacon.js

我目前正在为在线发布商开发第三方小部件,并希望跟踪其在不同网站上的效果。我想过实现Upworthy的代码(http://upworthy.github.io/2014/06/implementing-attention-minutes-part-1/)来计算我的小部件上每个用户的平均时间。所以我决定分三个步骤实现它:

  1. 当我的小部件仅在视口中可见时(我的小部件通常嵌入在文章的底部),启动我的“焦点”事件。
  2. 接下来,如Upworthy的方法所示,我将我的自定义“焦点”事件与模糊事件合并,以开发一个isFocused事件。
  3. 然后,我可以实现他们最近的活动方法,以确定用户是否点击了小部件,即他们是否正在与我的小部件进行交互。
  4. 我已经实现了上述内容,源代码可以在这里找到:http://jsfiddle.net/q21gzjmf/13/

    //simple jquery function to detect when my widget is visible   
      function widgetFocus(){ 
      $(window).scroll(function () {
    
      if ($(window).scrollTop() + $(window).height() > $('.footer').offset().top) {
        alert('visible');
        return true;
      }
      else{
        return false;
      }
      });
         }
    
    function merge(stream1, stream2) {
    return stream1.merge(stream2);
      }
    
    function eventStream(eventName) {
    return $(window).asEventStream(eventName);
     }
    
      var isFocused = eventStream("focus").map(widgetFocus)
      .merge(eventStream("blur").map(false))
      .toProperty(true);
    
      var EVENT_NAMES = ["focus", "click", "scroll", "mousemove", "touchstart", "touchend", "touchcancel", "touchleave", "touchmove"];
      var streams = _.map(EVENT_NAMES, eventStream);
      var interactionStream = _.reduce(streams, merge);
    
    
      var recentlyActive = interactionStream
      .map(true)
      .flatMapLatest(function() {
        return Bacon.once(true).merge(Bacon.once(false).delay(DECAY));
      })
      .toProperty(false);
    
      var isActive = (recentlyActive.and(isFocused));
    
      var secondsActive = Bacon.mergeAll(isActive.changes(), isActive.sample(1000))
    .map(function(isActive) {
      return {
        isActive: isActive,
        timestamp: new Date().getTime()
      };
    })
    .slidingWindow(2,2)
    .filter(function(span) { return span[0].isActive; })
    .map(function(span) { return span[1].timestamp - span[0].timestamp; })
    .scan(0, function(x,y) { return x + y;})
    .map(function(x) { return x / 1000; }) // milliseconds
    .map(Math.floor);
    
    secondsActive.assign($("#seconds"), "text");
    

    但是,如果向下滚动到底部,您会发现花费的时间计算为0并且不会动态更新,这与此处显示的Upworthy实现http://jsfiddle.net/zanes/mbGBr/不同。 我对功能反应式编程的概念很新,并且仍然试图了解Bacon.JS,所以我确信我必须犯了一个非常愚蠢的概念错误但我在这里要学习。任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:2)

重复步骤在窗口小部件可见时启动焦点事件

为此我们需要一个谓词来返回是否可见小部件:

function widgetFocus(){ 
  return $(window).scrollTop() + $(window).height() > $('.footer').offset().top;
}

然后我们继续处理所有与滚动相关的事件:

function eventStream(eventName) {
  return $(window).asEventStream(eventName);
}

var EVENT_NAMES = ["focus", "click", "scroll", "mousemove", "touchstart", "touchend", "touchcancel", "touchleave", "touchmove"];
var streams = _.map(EVENT_NAMES, eventStream);
var scrollEvents = Bacon.mergeAll(streams)
  .debounceImmediate(50); // we debounce here, so we didn't get too much events

接下来,您可以映射scrollEvents以获取布尔流,以指示是否显示小部件。

var isVisible$ = scrollEvents
  .map(function () {
    return widgetFocus();
  })
  .toProperty(false);

var secondsVisible = isVisible$.changes()
  .map(function(isActive) {
    return {
      isActive: isActive,
      timestamp: new Date().getTime()
    };
  })
  .slidingWindow(2,2)
  .filter(function(span) { return span[0].isActive; })
  .map(function(span) { return span[1].timestamp - span[0].timestamp; })
  .scan(0, function(x,y) { return x + y;})
  .map(function(x) { return Math.floor(x / 1000); }); // milliseconds

secondsVisible.log("visible: ");

这是第一步,可能已经适合你了。

下一步是创建一个属性或流,以确定页面是否处于活动状态(简单版本的Upworthy):

var DECAY = 1000;

function decayingStream() {
  return Bacon.once(true).merge(Bacon.once(false).delay(DECAY));
}

var isActive$ = scrollEvents
  .flatMapLatest(decayingStream)
  .toProperty(true);

接下来,我们可以合并isActive$isVisible$属性。我们只对时间窗口小部件可见且页面处于活动状态感兴趣!

var secondsVisibleAndActive = isActive$.and(isVisible$).changes()
  .map(function(isActive) {
    return {
      isActive: isActive,
      timestamp: new Date().getTime()
    };
  })
  .slidingWindow(2,2)
  .filter(function(span) { return span[0].isActive; })
  .map(function(span) { return span[1].timestamp - span[0].timestamp; })
  .scan(0, function(x,y) { return x + y;})
  .map(function(x) { return Math.floor(x / 1000); }); // milliseconds

secondsVisibleAndActive.log("active & visible: ");
secondsVisibleAndActive.assign($("#seconds"), "text");

与往常一样,从简单开始。


或者你可以举例说明Upworthy的实现之美:最后一行:

var hasAttention = (recentlyActive.and(isFocused)).or(videoIsPlaying);

在您的情况下,您可以将其更改为:

var widgetHasAttention = recentlyActive.and(isFocused).and(widgetIsVisible);

工作jsfiddle:http://jsfiddle.net/xkn3sc6y/3/