【问题标题】:Errors with greensock tween activated by mouse in pixi.js在 pixi.js 中被鼠标激活的 greensock 补间错误
【发布时间】:2025-12-10 14:20:03
【问题描述】:

我正在尝试在 pixi 中滚动 greensock 补间。我在尝试将获取鼠标/箭头输入 (trackpad.value) 的代码与我的补间挂钩时遇到错误。

这是我的工作 greensock 测试补间,以确保我在 pixi 中有 greensock 工作:(必须补间 pixi 中的位置元素):

var t1 = new TimelineMax({onUpdate:animate, onUpdateScope:stage});
 t1.to(bg.position, 3, {y:100});

这是我试图将 trackpad.value 挂接到 greensock 代码的代码(我收到以下错误:Uncaught TypeError: bg.position is not a function)

trackpad = new Trackpad(document);
var t1 = new TimelineMax({paused:true, onUpdate:animate, onUpdateScope:stage});   
  t1.progress(bg.position( Math.abs( trackpad.value ) / 3240));

然后我尝试了以下方法 - 它不起作用(但我没有收到错误):

var moveIt = trackpad.value / 3240;
  t1.progress(bg.position, moveIt, {});

这是定义触控板值的代码:

/*
 *  param: the html element that will be scrolled
 */
Trackpad = function(target)
{
    this.target = target;
    this.value = 0;
    this.easingValue = 00;
    this.dragOffset = 0;
    this.dragging;
    this.speed= 0;
    this.prevPosition = 0;

    $(this.target).mousedown($.proxy(this.onMouseDown, this));
    this.target.onmousewheel = $.proxy(this.onMouseWheel, this);

    // not forgetting touchs!
    this.target.ontouchstart = $.proxy(this.onTouchStart, this);

    // stop dragging!
    $(document).keydown( $.proxy(this.onArrow, this))//function(e){

    //this.target.ondragstart = function(){return false;}
}

// set constructor
Trackpad.constructor = Trackpad;

// create the functions

Trackpad.prototype.unlock = function()
{
    this.locked = false;
    this.speed = 0;
    this.easingValue = this.value;
}

Trackpad.prototype.lock = function()
{
    this.locked = true;
}

Trackpad.prototype.update = function()
{
    if(this.easingValue > 0)this.easingValue = 0;
    if(this.easingValue < -10700)this.easingValue = -10700;
    this.value = this.easingValue;

    if(this.dragging)
    {
        var newSpeed = this.easingValue - this.prevPosition;
        newSpeed *= 0.7;

        this.speed += (newSpeed - this.speed) *0.5;//+= (newSpeed - this.speed) * 0.5;
        this.prevPosition = this.easingValue;
    }
    else
    {
        this.speed *= 0.9;
        this.easingValue +=  this.speed;

        if(Math.abs(this.speed) < 1)this.speed = 0;
    }
}

Trackpad.prototype.onArrow = function(event)
{
     if (event.keyCode == 38) { 
     // UP
     this.speed = 4;
       return false;
    }
    else  if (event.keyCode == 40) { 
     // UP
     this.speed -= 4
       return false;
    }
}

Trackpad.prototype.onMouseWheel = function(event)
{
    event.preventDefault();
    this.speed = event.wheelDelta * 0.1;
}


Trackpad.prototype.startDrag = function(newPosition)
{
    if(this.locked)return;
    this.dragging = true;
    this.dragOffset = newPosition - this.value; 
}

Trackpad.prototype.endDrag = function(newPosition)
{
    if(this.locked)return;
    this.dragging = false;
}

Trackpad.prototype.updateDrag = function(newPosition)
{
    if(this.locked)return;
    this.easingValue = (newPosition - this.dragOffset);
}

/*
 * MOUSE
 */
Trackpad.prototype.onMouseDown = function(event)
{
    if(event)event.preventDefault();

    event.returnValue = false;

    $(document).mousemove($.proxy(this.onMouseMove, this));
    $(document).mouseup($.proxy(this.onMouseUp, this));

    this.startDrag(event.pageY);    
}

Trackpad.prototype.onMouseMove = function(event)
{
    if(event)event.preventDefault();
    this.updateDrag(event.pageY);
}

Trackpad.prototype.onMouseUp = function(event)
{   
    //$(this.target).mousemove(null);
    $(document).unbind('mousemove');
    $(document).unbind('mouseup');
    //this.target.onmousemove = null;

    this.endDrag();// = false;
}

/*
 * TOUCH!
 */
Trackpad.prototype.onTouchStart = function(event)
{
    //event.preventDefault();

    this.target.ontouchmove = $.proxy(this.onTouchMove, this);
    this.target.ontouchend = $.proxy(this.onTouchEnd, this);

    this.startDrag(event.touches[0].clientY);
}

Trackpad.prototype.onTouchMove = function(event)
{

    event.preventDefault();
    this.updateDrag(event.touches[0].clientY);
}

Trackpad.prototype.onTouchEnd = function(event)
{
    this.target.ontouchmove = null;
    this.target.ontouchend = null;
    this.endDrag();
}

** 编辑

tl = new TimelineLite( { paused: true } );

 // respond to scroll event - in this case using jquery
 $(window).scroll();
//apply whatever math makes the most sense to progress the timeline progress from 0 to 1 within those parameters. Something like,
$(window).scroll( function() {
    var st = $(this).scrollTop();
    if ( st < someArbitraryValue ) { // someArbitraryValue, where to start
        // Here, "someOtherArbitaryValue" would be the
        // "height" of the scroll to react to
        tl.progress( Math.abs( st ) / someOtherArbitaryValue );
     }
 });

【问题讨论】:

标签: javascript canvas gsap pixi.js


【解决方案1】:

this 是你追求的那种效果吗?

JavaScript:

window.requestAnimFrame=(function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(callback){window.setTimeout(callback,1000/60);};})(); //http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
var stageWidth=$(window).innerWidth();
var stageHeight=$(window).innerHeight();
var renderer=PIXI.autoDetectRenderer(stageWidth,stageHeight);
var bg,cat,moon,blue,trackpad,texture1,texture2,texture3;
document.body.appendChild(renderer.view);
texture1=PIXI.Texture.fromImage('https://dl.dropboxusercontent.com/u/45891870/Experiments/*/1.5/cat.jpg');
texture2=PIXI.Texture.fromImage('https://dl.dropboxusercontent.com/u/45891870/Experiments/*/1.5/moon.jpg');
texture3=PIXI.Texture.fromImage('https://dl.dropboxusercontent.com/u/45891870/Experiments/*/1.5/blue.jpg');
bg=new PIXI.Container();
cat=new PIXI.Sprite(texture1);
moon=new PIXI.Sprite(texture2);
blue=new PIXI.Sprite(texture3);
cat.anchor.x=cat.anchor.y=moon.anchor.x=moon.anchor.y=blue.anchor.x=blue.anchor.y=0;
cat.position.x=cat.position.y=moon.position.x=blue.position.x=bg.position.x=bg.position.y=0;
cat.width=moon.width=blue.width=stageWidth;
moon.position.y=1080;
blue.position.y=2160;
bg.addChild(cat);
bg.addChild(blue);
bg.addChild(moon);
bg.vy=bg.vx=0;//what are those?
trackpad=new Trackpad(document);
requestAnimFrame(animate);
function animate(){
    requestAnimFrame(animate);
    bg.position.y=trackpad.value;
    trackpad.update();
    renderer.render(bg);
}

让我知道这是否正是您正在寻找的东西,然后我会根据与您的代码相比发生的变化为您分解它。

注意事项:

  1. 首先,我在上面的示例中使用了 Pixi.JS 的最新版本 (v3.0.6)。此 v3 更新带来了一些重大变化。其中几个对您的问题很突出的是:
    • 不再需要Stage 对象来进行渲染。任何Container 类型的对象都可以直接用于在画布上渲染。
    • 将名称 DisplayObjectContainer 缩短为简单的 Container。这可能是您尝试在您在 cmets 中提到的环境中实现我的代码时遇到错误的原因,因为我认为您使用的是旧版本之一。
    • 阅读有关此更新的所有信息 hereherehere
  2. 我总是喜欢使用最新最好的 GSAP (v1.17.0‏)。甚至这个框架的 dot 版本也带来了重大更新,这就是我喜欢让它保持最新的原因。阅读有关此更新的重要说明 here。话虽如此,当前的实现并没有真正使用TweenMax
  3. TweenMax 捆绑了 EasePackCSSPlugin 和其他一些东西。无需单独加载它们。相应地更新您的 HTML。使用Peter Tichy 提供的这个方便的GSAP CheatSheet 获取此类信息以及有关此工具的更多信息。
  4. Trackpad.js 的变化:
    • update 方法中,定义了页面可以向上滚动的最大滚动限制。该值以前是 -10700。我将其更改为 -2160。我认为,您可能希望将其设置为 -3240,这取决于我对您要达到的目标的了解。
    • 格式更改。
  5. ma​​in.js 中的变化(无论您给主脚本文件起什么名字):
    • 感谢 Paul Irish 添加了 requestAnimationFrame polyfill
    • 删除了var stage= new PIXI.Stage(0xff00ff); 行。阅读上面的#1了解详情。
    • 已将 DisplayObjectContainer 重命名为 Container,后者已分配给 bg。阅读上面的#1了解详情。
    • animate 循环中添加了bg.position.y=trackpad.value;。你错过了这个。您需要使用trackpad.value 来定位您的bg
    • 在同一 animate 循环中添加了 trackpad.update();。这是一个大问题,恕我直言,这是您未能理解其目的的一个。总之,Trackpad.js 需要及时更新其value,并且由于requestAnimFrame,您运行的唯一循环是animate 循环。因此,update(); 方法被调用。
    • 呈现bg 而不是stage。阅读上面的#1了解详情。
    • 格式更改。

如果有什么不清楚的地方请告诉我。

T

【讨论】:

  • 非常感谢 Tahir 看到这个 - 在本地,我在第 10 行 Uncaught TypeError: undefined is not a function(anonymous function) @ app2.js:10 遇到了上述错误 - 我明天会尝试在本地复制小提琴。我刚注意到小提琴,是的!就是这样,在每次鼠标滚动/拖动或任何输入的开始和结束时都会稍微放慢一点 easedInOut 速度。我需要将它设置为使用greensock(每个补间都有一个开始和结束位置值),这样我也可以向它添加更多的greensock补间。非常感谢塔希尔。非常非常有帮助。
  • @AgentZebra,在上面添加了注释EaseTrackpad.js 本身中计算。它吐出的value 已经将某些easing 考虑在内。如果我清楚地理解了你,请阅读我之前关于using TimelineMax in accordance with scroll 主题的回答。
  • 非常感谢 Tahir,我想我现在明白了。我非常感谢您花时间如此清楚地解释一切。我仍然不确定如何用这个实现greensock,以及你之前在pixi中的示例,因为它是基于浏览器而不是基于画布的。
  • @AgentZebra 没问题。您对先前的答案专注于浏览器的“滚动”事件是正确的,但如果我错了,请纠正我,您想听 mousemovemousewheelkeyboardtouch 事件尝试拖动或滚动您的画布并在此基础上,您想定位一个 GSAP TimelineMax 对应于其先前的位置
  • 我想为每个时间线Max tween 设置一个开始和结束位置变量,并让它们由mousemove, mousewheel, keyboard &amp; touch 事件驱动。 [这是我想在 pixi 中执行的浏览器版本] (codepen.io/adrianparr/pen/mbrqt) 见我的**edit to the initial question above (at the bottom of the question),您可以看到如何在浏览器中执行此操作的一个想法(但不是在 pixi 中) - 我想复制这个在pixi中。
【解决方案2】:

我想编辑旧答案,但决定不这样做,因为我认为它回答了你原来的问题。

查看Codepen demo 以了解解决同一问题的新方法。我真的很希望听取社区关于我在听取事件和使用它们来调整 GSAP 时间表方面所采取的方法。

我的示例中使用了 4 个 JS 文件:app.jsconstants.jstimeline.jslisteners.js。可以在演示的 JavaScript 编辑器的设置齿轮图标中找到链接。所有这些文件都带有我在互联网上找到的针对特定问题的解决方案的链接。

在这些文件中,app.js的代码如下:

JavaScript:

function Application(){}
Application.prototype.init=function(){
    this.constants=Constants.getInstance();
    this.BASE_URL=this.constants.BASE_URL;
    this.IMAGE_JS_URL=this.constants.IMAGE_JS_URL;
    this.IMAGE_PIXI_URL=this.constants.IMAGE_PIXI_URL;
    this.IMAGE_GSAP_URL=this.constants.IMAGE_GSAP_URL;
    this.createPolyfillForBind();
    this.setupRenderer();
    this.loadImages();
};
Application.prototype.setupRenderer=function(){
    this.stageWidth=window.innerWidth;
    this.stageHeight=window.innerHeight;
    //this.renderer=PIXI.autoDetectRenderer(this.stageWidth,this.stageHeight);
    this.renderer=new PIXI.CanvasRenderer(this.stageWidth,this.stageHeight);
    document.body.appendChild(this.renderer.view);
};
Application.prototype.loadImages=function(){
    var self=this;
    this.loader=new PIXI.loaders.Loader(this.BASE_URL,1,{crossOrigin:''}); // PIXI Loader class [http://pixijs.github.io/docs/PIXI.loaders.Loader.html]
    this.loader.add(this.IMAGE_JS_URL); // Loader extends ResourceLoader [http://adireddy.github.io/docs/haxe-pixi/v3/types/pixi/plugins/resourceloader/ResourceLoader.html]
    this.loader.add(this.IMAGE_PIXI_URL);
    this.loader.add(this.IMAGE_GSAP_URL);
    //this.loader.once('complete',function(){self.onImagesLoaded.apply(self);}); // Vanilla JS alternative to jQuery's proxy() method [http://*.com/a/4986536]
    this.loader.once('complete',this.onImagesLoaded.bind(this)); // bind() polyfill [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Polyfill]
    this.loader.load();
};
Application.prototype.onImagesLoaded=function(){
    this.setupSprites();
    this.initTimeline();
    this.initListeners();
    this.startTicker();
};
Application.prototype.setupSprites=function(){
    this.containerBg=new PIXI.Container();
    this.spriteJS=new PIXI.Sprite(PIXI.utils.TextureCache[this.BASE_URL+this.IMAGE_JS_URL]); // TextureCache in action [http://www.html5gamedevs.com/topic/7674-load-textures-synchronously/?p=45836]
    this.spritePIXI=new PIXI.Sprite(PIXI.utils.TextureCache[this.BASE_URL+this.IMAGE_PIXI_URL]); // PIXI.TextureCache became PIXI.utils.TextureCache in v3 [http://www.html5gamedevs.com/topic/14144-v3-utilstexturecache-utils-is-not-defined/?p=80524]
    this.spriteGSAP=new PIXI.Sprite(PIXI.utils.TextureCache[this.BASE_URL+this.IMAGE_GSAP_URL]);
    this.containerBg.addChild(this.spriteJS);
    this.containerBg.addChild(this.spritePIXI);
    this.containerBg.addChild(this.spriteGSAP);
    this.spriteJS.anchor.x=this.spriteJS.anchor.y=this.spritePIXI.anchor.x=this.spritePIXI.anchor.y=this.spriteGSAP.anchor.x=this.spriteGSAP.anchor.y=0;
    this.spriteJS.position.x=this.spriteJS.position.y=this.spritePIXI.position.x=this.spriteGSAP.position.x=this.containerBg.position.x=this.containerBg.position.y=0;
    this.scaleImage(this.spriteJS);
    this.scaleImage(this.spritePIXI);
    this.scaleImage(this.spriteGSAP);
    this.spritePIXI.alpha=this.spriteGSAP.alpha=0;
    this.spriteJS.position.y=this.constants.GUTTER;
    this.spritePIXI.position.y=this.spriteJS.height*2+this.constants.GUTTER;
    this.spriteGSAP.position.y=this.spriteJS.height+this.spritePIXI.height*2+this.constants.GUTTER;
};
Application.prototype.scaleImage=function(sprite){
    //var scale=Math.min(this.stageWidth/sprite.width,this.stageHeight/sprite.height); // resize with aspect ratio [http://community.createjs.com/discussions/createjs/547-resizing-canvas-and-its-content-proportionally-cross-platform#comment_27266530] and [https://opensourcehacker.com/2011/12/01/calculate-aspect-ratio-conserving-resize-for-images-in-javascript/]
    var scale=this.stageWidth/sprite.width;
    sprite.scale.x=sprite.scale.y=scale;
};
Application.prototype.initTimeline=function(){
    this.timeline=new Timeline();
    this.timeline.init(this.containerBg,this.spriteJS,this.spritePIXI,this.spriteGSAP,this.stageWidth,this.stageHeight);
};
Application.prototype.initListeners=function(){
    var self=this;
    //this.listeners=new Listeners();
    //this.constants.setListenersObject(this.listeners);
    //this.listeners.init();
    this.listeners=Listeners.getInstance();
    this.listeners.addListeners();
    document.addEventListener(this.constants.SCROLLED,this.onScroll.bind(this),false);
    document.addEventListener(this.constants.STARTED_DRAG,this.onStartDrag.bind(this),false);
    document.addEventListener(this.constants.DRAGGED,this.onDrag.bind(this),false);
    document.addEventListener(this.constants.END_DRAG,this.onEndDrag.bind(this),false);
};
Application.prototype.onScroll=function(e){ this.timeline.onScroll(e); };
Application.prototype.onStartDrag=function(e){ this.timeline.onStartDrag(e); };
Application.prototype.onDrag=function(e){ this.timeline.onDrag(e); };
Application.prototype.onEndDrag=function(e){ this.timeline.onEndDrag(e); };
Application.prototype.startTicker=function(){
    var self=this;
    //TweenLite.ticker.addEventListener('tick',function(){self.render.apply(self);},false); // Vanilla JS alternative to jQuery's proxy() method [http://*.com/a/4986536]
    TweenLite.ticker.addEventListener('tick',this.render.bind(this),false);
};
Application.prototype.render=function(){this.renderer.render(this.containerBg);};
Application.prototype.createPolyfillForBind=function(){ // [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind#Polyfill]
    if(!Function.prototype.bind){
        Function.prototype.bind=function(oThis){
            if(typeof this!=='function'){
                // closest thing possible to the ECMAScript 5
                // internal IsCallable function
                throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
            }
            var aArgs=Array.prototype.slice.call(arguments,1),
                fToBind=this,
                fNOP=function(){},
                fBound=function(){
                    return fToBind.apply(this instanceof fNOP
                            ?this
                            :oThis,
                        aArgs.concat(Array.prototype.slice.call(arguments)));
                };
            fNOP.prototype=this.prototype;
            fBound.prototype=new fNOP();
            return fBound;
        };
    }
};
//
var app=new Application();
app.init();

附:在同一个示例中,我还大量尝试了设计模式,主要是PrototypeSingleton 模式。我也期待来自社区的 cmets。

T

【讨论】:

  • 谢谢 Tahir,我终于回到了这个话题。这是非常棒的,非常棒的工作。我非常感谢您花这么多时间回答我的问题。我有点不知所措。它非常复杂,新手很难理解。对于我正在尝试做的事情,感觉有点矫枉过正。我真的想为以下评论创建一个非常简单的 pixi implimentation:(在上述问题的底部重复我的** edit),超级简单,所以我可以理解它:D,但也从 value 获取触控板.js。顺便说一句,你的 codepen 上的其他工作超级棒,一流!
  • tl = new TimelineLite( { paused: true } ); // respond to scroll event - in this case using jquery $(window).scroll(); //apply whatever math makes the most sense to progress the timeline progress from 0 to 1 within those parameters. Something like, $(window).scroll( function() { var st = $(this).scrollTop(); if ( st &lt; someArbitraryValue ) { // someArbitraryValue, where to start // Here, "someOtherArbitaryValue" would be the // "height" of the scroll to react to tl.progress( Math.abs( st ) / someOtherArbitaryValue ); } });
  • 感谢@AgentZebra 的好话。欣赏它。是的,我知道这对于我们在这里讨论的用例来说肯定是矫枉过正。但我一直想进入设计模式,这对我来说似乎也是一个尝试它们的好机会。两只鸟一块石头,你知道的。所以,虽然我同意你的情况,这绝对是有点太多了,但我强烈建议你尽可能地拿走它。我在上面提到的所有 JS 文件的实现旁边的代码中放置了大量有用的链接。因此,为了学习目的,请挖掘它们。
  • 当我找到空闲时间时,我一定会查看您在问题中提出的新 编辑。再次感谢。
最近更新 更多