我正在尝试使用mouseMove事件围绕原点旋转三 Angular 形。
我使用touchstarttouchmove事件获取触摸起点和当前触摸点,然后使用以下方法轻松找到方向 Angular :

alpha = y2 - y1 / x2 - x1;     // alpha is the tangent of the angle
beta= atan(alpha);      // beta is the angle in radians

然后我旋转PIXI中的元素:
function animTriangle (beta) {

  // initially clear the previous shape
  myTriangle.clear();


  // draw a new shape in new positions
  myTriangle.beginFill(0x000000, 0.1);
  myTriangle.moveTo(origin.x, origin.y);
  myTriangle.lineTo(origin.x - 100, 0);
  myTriangle.lineTo(origin.x + 100, 0);
  myTriangle.lineTo(origin.x, origin.y);
  myTriangle.endFill();
  myTriangle.rotation = beta;

}

我正在使用RequestAnimationFrame循环管理我的画。

问题是动画不稳定,我需要旋转惯性。如何解决此功能?

最佳答案

惯性,加速和拖动

我使用的一种方法是使用模拟加速度和阻力(阻力)值的deltaV创建追赶所需值的追赶值。请注意,这是一个简单的模拟。

逐步

定义所需的值

var rotate = ?; // the value input by the user
var rotateChase; // this is the chasing value representing the displayed control
var rotateDelta; // this is the change in chase per frame
const rotateDrag = 0.4; // this is the friction or drag
const rotateAcceleration = 0.9; // this is how quickly the display responds

阻力是一个大于0且 = 0.5会导致追赶值在所需值附近反弹,随着阻力值移向1,反弹会变得越来越明显。

加速度值的范围大于0且
拖动和加速都相互影响。
为了
  • 快速 react accel = 0.9drag = 0.49
  • 轻慢响应accel = 0.1drag = 0.49
  • 重度慢响应accel = 0.02drag = 0.49
  • 硬弹性响应accel = 0.7drag = 0.7
  • 缓慢弹性响应accel = 0.1drag = 0.7

  • 更新

    然后一帧,通过添加deltaV加速到输入值,
    rotateDelta += (rotate - rotateChase) * rotateAcceleration;
    

    通过减少其变形来将拖动添加到三 Angular 洲。
    rotateDelta *= rotateDrag;
    

    然后将deltaV添加到跟踪值中,
    rotateChase += rotateDelta;
    

    现在可以显示追踪值了
    myTriangle.rotation = rotateChase;
    

    添加边界

    那是一个无限的追逐者。要将边界设置为该值,需要一些额外的代码。首先通过设置最小值和最大值来描述边界。
    var rotateMin = 0;
    var rotateMax = Math.PI*2;
    

    然后,通过定义反射或反射来描述值越界时的行为。 0使它停止在边界处停止, = -1将使两端产生一个小的反弹。其他值创造有趣的效果
    var rotateReflect = -0.5;
    

    然后是控制该行为的代码
    if (rotateChase < rotateMin) {
       rotateChase = rotateMin;  // set to the min val
       if(rotateDelta < 0){      // only if delta is negative
           rotateDelta *= rotateReflect;
       }
    }else
    if (rotateChase > rotateMax) {
       rotateChase = rotateMax;  // set to the max
       if(rotateDelta > 0){      // only if delta is positive
           rotateDelta *= rotateReflect;
       }
    }
    

    显示和使用

    现在,您可以选择要使用哪个值作为控件的输入。 rotateChaserotate都可以使用,但是rotateChase可能需要一些时间才能解决。我要做的是从rotateChase值中获取一个四舍五入的值,该值消除了追逐的细节。

    例如,如果控件用于音量
    device.volume = Number(rotateChase.toFixed(3));
    

    简化

    所有的接缝都是为了一项值(value)而进行的大量工作。但是我们是程序员,本质上是懒惰的,所以让我们将其划分为一个简单的惯性类
    // Define a Inertia object. Set Answer for details.
    // Has methods
    // update(input); Called once pre animation frame with input being the value to chase
    // setValue(input); Hard sets the chasing value. Not drag or inertia
    // Has properties
    // value;  The chasing value bounds checked
    function Inertia (min, max, acceleration, drag, reflect) {
        // some may question why the constants, why not use the closure on arguments
        // Reason: Some JS environments will fail to optimise code if the input
        //         arguments change. It may be tempting to extend this Object to
        //         change the min, max or others. I put this here to highlight the
        //         fact that argument closure variables should not be modified
        //         if performance is important.
        const ac = acceleration;  // set constants
        const dr = drag;
        const minV = min;
        const maxV = max;
        const ref = -Math.abs(reflect); // ensure a negative. Why? because I always forget this is a negative.
        this.value = min;
        var delta = 0;
    
        this.update = function (input) {
             delta += (input - this.value) * ac;
             delta *= dr;
             this.value += delta;
             if (this.value < minV) {
                 this.value = minV;
                 if(delta < 0){
                     delta *= ref;
                 }
             } else
             if (this.value > maxV) {
                 this.value = maxV;
                 if(delta > 0){
                     delta *= ref;
                 }
             }
             return this.value;
         };
         // this move the value to the required value without any inertial or drag
         // is bound checked
         this.setValue = function (input) {
             delta = 0;
             this.value = Math.min(maxV, Math.min(minV, input));
             return this.value;
         }
     }
    

    使用上面的代码
    // in init
    var rotater = new Inertia(0, Math.PI*2, 0.9, 0.4, -0.1);
    
    // in the animation frame
    myTriange = rotater.update(beta);
    

    更新

    我添加了一些代码来显示各种设置以及它们如何影响惯性。该代码并非旨在作为代码风格或DOM接口(interface)最佳实践的示例,因为在这两个方面都远远不够。它使用了我上面介绍的惯性对象。您可以在演示代码的顶部找到该对象。

    最好观看该演示全屏

    //------------------------------------------------------
    // Function from answer Inertia
    // Define a Inertia object. Set Answer for details.
    // Has methods
    // update(input); Called once pre animation frame with input being the value to chase
    // set(input); Hard sets the chasing value. Not drag or inertia
    // Has properties
    // value;  The chasing value bounds checked
    function Inertia (min, max, acceleration, drag, reflect) {
        // some may question why the constants, why not use the closure on arguments
        // Reason: Some JS environments will fail to optimise code if the input
        //         arguments change. It may be tempting to extend this Object to
        //         change the min, max or others. I put this here to highlight the
        //         fact that argument closure variables should not be modified
        //         if performance is important.
        const ac = acceleration;  // set constants
        const dr = drag;
        const minV = min;
        const maxV = max;
        const ref = -Math.abs(reflect); // ensure a negative. Why? because I always forget this is a negative.
        this.value = min;
        this.quiet = true;
        var delta = 0;
    
        this.update = function (input) {
             delta += (input - this.value) * ac;
             delta *= dr;
             this.value += delta;
             if (this.value < minV) {
                 this.value = minV;
                 if(delta < 0){
                    delta *= ref;
                 }
             } else
             if (this.value > maxV) {
                 this.value = maxV;
                 if(delta > 0){
                     delta *= ref;
                 }
             }
             if(Math.abs(delta) < (maxV-minV)*0.001 && Math.abs(this.value-input) < 0.1 ){
                 this.quiet = true;
             }else{
                 this.quiet = false;
             }
             return this.value;
         };
         // this move the value to the required value without any inertial or drag
         // is bound checked
         this.setValue =  function (input) {
             delta = 0;
             this.quiet = true;
             this.value = Math.min(maxV, Math.max(minV, input));
             return this.value;
         }
     }
    // End of answer
    //--------------------------------------------------------
    
    
    
    
    
    
    
    // All the following code is not part of the answer.
    // I have not formatted, commented, and thoroughly tested it
    
    
    /** MouseFullDemo.js begin **/
    var canvasMouseCallBack = undefined;  // if needed
    function createMouse(element){
        var demoMouse = (function(){
            var mouse = {
                x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
                lx:0,ly:0,
                interfaceId : 0, buttonLastRaw : 0,  buttonRaw : 0,
                over : false,  // mouse is over the element
                bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
                getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
                startMouse:undefined,
            };
            function mouseMove(e) {
                //console.log(e)
                var t = e.type, m = mouse;
                m.lx = e.offsetX; m.ly = e.offsetY;
                m.x = e.clientX; m.y = e.clientY;
                m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
                if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
                } else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
                } else if (t === "mouseout") {m.over = false;
                } else if (t === "mouseover") { m.over = true;
                } else if (t === "mousewheel") { m.w = e.wheelDelta;
                } else if (t === "DOMMouseScroll") { m.w = -e.detail;}
                if (canvasMouseCallBack) { canvasMouseCallBack(m.x, m.y); }
                e.preventDefault();
            }
            function startMouse(element){
                if(element === undefined){
                    element = document;
                }
                "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",").forEach(
                function(n){element.addEventListener(n, mouseMove);});
                element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
            }
            mouse.mouseStart = startMouse;
            return mouse;
        })();
        demoMouse.mouseStart(element);
        return demoMouse;
    }
    /** MouseFullDemo.js end **/
    
    
    var cellSize = 70;
    var createImage=function(w,h){
        var i=document.createElement("canvas");
        i.width=w;
        i.height=h;
        i.ctx=i.getContext("2d");
        return i;
    }
    var drawCircle= function(img,x,y,r,col,colB,col1,width){
        var c = img.ctx;
        var g;
        c.lineWidth = width;
        c.strokeStyle = col1;
        g = c.createRadialGradient(x,y,1,x,y,r);
        g.addColorStop(0,col);
        g.addColorStop(1,colB);
        c.fillStyle = g;
        c.beginPath();
        c.arc(x,y,r-width*3,0,Math.PI*2);
        c.fill();
    
        c.strokeStyle = col1;
        c.fillStyle = col1;
        c.fillRect(x,y-width,r,width*2)
        c.fillStyle = col;
        c.fillRect(x+width,y-width/2,r,width)
    }
    var drawCircleO= function(img,x,y,r,col,colB,col1,width){
        var c = img.ctx;
        var g = c.createRadialGradient(x+r*0.21,y+r*0.21,r*0.7,x+r*0.21,y+r*0.21,r);
        g.addColorStop(0,"black");
        g.addColorStop(1,"rgba(0,0,0,0)");
        c.fillStyle = g;
        c.globalAlpha = 0.5;
        c.beginPath();
        c.arc(x+r*0.21,y+r*0.21,r,0,Math.PI*2);
        c.fill();
        c.globalAlpha = 1;
        var g = c.createRadialGradient(x*0.3,y*0.3,r*0.5,x*0.3,y*0.3,r);
        g.addColorStop(0,col);
        g.addColorStop(1,colB);
        c.lineWidth = width;
        c.strokeStyle = col1;
        c.fillStyle = g;
        c.beginPath();
        c.arc(x,y,r-width,0,Math.PI*2);
        c.stroke();
        c.fill();
    }
    // draws radial marks with miner markes
    // len,col and width are arrays
    var drawCircleMarks= function(img,x,y,r,start,end,col,width,length,number,miner){
        var i,vx,vy,count,style,len;
        var c = img.ctx;
        var step = (end-start)/number;
        count = 0;
    
        end += step/2; // add to end to account for floating point rounding error
        for(i = start; i <= end; i+= step){
            vx = Math.cos(i);
            vy = Math.sin(i);
            if(count % miner === 0){
                style = 0;
            }else{
                style = 1;
            }
            c.strokeStyle = col[style];
            c.lineWidth = width[style];
            len = length[style];
            c.beginPath();
            c.moveTo(vx*r+x,vy*r+y);
            c.lineTo(vx*(r+len)+x,vy*(r+len)+y);
            c.stroke();
            count += 1;
    
        }
    }
    
    var defaultMap = {
        number:function(num,def){
            if( isNaN(num) ){
                return def
            }
            return Number(num);
        },
        colour:function(col,def){
            // no much code for demo so removed
            if(col === undefined || typeof col !== "string"){
                return def;
            }
            return col;
        },
        "ticks":{
            validate:function(val){
                return val===undefined?true:val?true:false;
            },
        },
        "type":{
            validate:function(val){
                switch (val) {
                    case "dial":
                    case "horizontal-slider":
                        return val;
                }
                return undefined
            }
        },
        "min":{
            validate:function(val){
                return defaultMap.number(val,0);
            }
        },
        "max":{
            validate:function(val){
                return defaultMap.number(val,100);
            }
        },
        "drag":{
            validate:function(val){
                return defaultMap.number(val,0.4);
            }
        },
        "reflect":{
            validate:function(val){
                return defaultMap.number(val,0.2);
            }
        },
        "accel":{
            validate:function(val){
                return defaultMap.number(val,0.4);
            }
        },
        "value":{
            validate:function(val){
                return defaultMap.number(val,0);
            }
        },
        "tick-color":{
            validate:function(val){
    
            }
        },
        "decimals":{
            validate:function(val){
                return defaultMap.number(val,0);
            }
        },
        "display":{
            validate:function(val){
                if(val === null || val === undefined || typeof val !== "string"){
                    return undefined;
                }
                return document.querySelector(val);
            }
        }
    }
    
    // validates user defined DOM attribute
    function getSafeAttribute(element,name){
        var val,def;
        if(name === undefined){
            return undefined;
        }
        def = defaultMap[name];
        if(def === undefined){ // unknown attribute
            if(element.attributes["data-"+name]){
                return element.attributes["data-"+name].value;
            }
            return undefined
        }
        if(element.attributes["data-"+name]){
            val = element.attributes["data-"+name].value;
        }
        return def.validate(val);
    }
    // Custom user control
    // Warning this can return a undefined control
    function Control(element,owner){
        var dialUpdate,drawFunc,w,h,nob,back,mouse,minSize,canvas,chaser,dragging,dragX,dragY,dragV,realValue,startP,endP,lastVal,reflect,drag,accel;
        var dialUpdate = function(){
            var unitPos = (this.value-this.min)/(this.max-this.min);
            canvas.ctx.setTransform(1,0,0,1,0,0);
            canvas.ctx.clearRect(0,0,w,h);
            canvas.ctx.drawImage(back,0,0);
            canvas.ctx.setTransform(1,0,0,1,back.width/2,back.height/2);
            canvas.ctx.rotate(unitPos *(endP-startP)+startP);
            canvas.ctx.drawImage(nob,-nob.width/2,-nob.height/2);
        }
    
        if(element === undefined){ // To my UNI mentor with love.. LOL
             return undefined;
        }
        this.type = getSafeAttribute(element,"type");
        if(this.type === undefined){
            return undefined;     // this is a non standared contrutor return
        }
    
        this.owner = owner; // expose owner
        // exposed properties
        this.min = getSafeAttribute(element,"min");
        this.max = getSafeAttribute(element,"max");
        this.ticks = getSafeAttribute(element,"ticks");
        this.tickColor = getSafeAttribute(element,"tick-color");
        this.value = realValue = getSafeAttribute(element,"value");
    
        this.display = getSafeAttribute(element,"display");
        if(this.display){
            var decimals  = getSafeAttribute(element,"decimals");
        }
        drag = getSafeAttribute(element,"drag");
        accel = getSafeAttribute(element,"accel");
        reflect = getSafeAttribute(element,"reflect");;
        chaser = new Inertia(this.min,this.max,accel,drag,reflect);
    
        w = element.offsetWidth;
        h = element.offsetHeight;
    
        canvas = createImage(w,h);
        minSize = Math.min(w,h);
        mouse = createMouse(element);
        if(this.type === "dial"){
            nob = createImage(minSize*(3/4),minSize*(3/4));
            drawCircle(nob,minSize*(3/4)*(1/2),minSize*(3/4)*(1/2),minSize*(3/4)*(1/2),"white","#CCC","black",3);
            back = createImage(minSize,minSize);
            startP = Math.PI*(3/4);
            endP = Math.PI*(9/4);
            drawCircleMarks(
                back,
                minSize/2,
                minSize/2,
                minSize/3,
                startP,
                endP,
                ["black","#666"],
                [2,1],
                [minSize*(1/4),minSize*(1/9)],
                16,
                4
            );
            drawCircleO(back,minSize*(1/2),minSize*(1/2),minSize*(3/4)*(1/2),"white","#aaa","black",3);
            drawFunc = dialUpdate.bind(this);
        }
        element.appendChild(canvas);
        this.active = true;
        this.resetChaser = function(min,max,accel1,drag1,reflect1){
            this.min = min===null?this.min:min;
            this.max = max===null?this.max:max;
            drag = drag1===null?drag:drag1;
            accel = accel1===null?accel:accel1;
            reflect = reflect1===null?reflect:reflect1;
            chaser = new Inertia(this.min,this.max,accel,drag,reflect);
    
            chaser.setValue(this.value);
            drawFunc();
        }
        this.update = function(){
            var inVal;
            if(mouse.over){
                element.style.cursor = "drag_ew";
            }
            if((this.owner.mouse.buttonRaw&1) === 1 && !dragging && mouse.over && this.owner.draggingID === -1){
                dragX = this.owner.mouse.x - (mouse.lx-w/2);
                dragY = this.owner.mouse.y - (mouse.ly-h/2);
                dragging = true;
                this.owner.draggingID = this.ID;
            }else
            if(this.owner.draggingID === this.ID && ((this.owner.mouse.buttonRaw&1) === 1 || (this.owner.mouse.buttonRaw&1) === 0) && dragging){
                inVal = (Math.atan2(this.owner.mouse.y-dragY,this.owner.mouse.x-dragX)+Math.PI*2);
                if(inVal > Math.PI*0.5+Math.PI*2){
                     inVal -= Math.PI*2;
                }
                realValue = inVal;
                realValue = ((realValue-startP)/(endP-startP))*(this.max-this.min)+this.min;
                if((this.owner.mouse.buttonRaw&1) === 0){
                    dragging = false;
                    this.owner.draggingID = -1;
                }
            }
            realValue = Math.min(this.max,Math.max(this.min,realValue));
            this.value = chaser.update(realValue);
    
            if(!chaser.quiet){
                drawFunc();
                if(this.display){
                    this.display.textContent = realValue.toFixed(decimals);
                }
                if(this.onchange !== undefined && typeof this.onchange === "function"){
                    this.onchange({value:realValue,target:element,control:this});
                }
            }
        }
        // force chaser to wake up
        chaser.setValue(this.value);
        drawFunc();
        element.control = this;
    }
    
    // find and create controllers
    function Controllers(name){
        var controls, elems, i, control, e;
        var ID = 0;
        controls = [];
        elems = document.querySelectorAll("."+name);
        for(i = 0; i < elems.length; i++){
            e = elems[i];
            control = new Control(e,this);
            control.ID = ID++;
            if(control !== undefined){
                controls.push(control);
            }
        }
        this.update = function(){
            controls.forEach(function(cont){
                cont.update();
            })
        }
        this.mouse = createMouse(document);
        this.draggingID = -1;
    }
    
    // get elements to play with the large control
    var c = new Controllers("testControl");
    var drag = document.getElementById("dragSetting");
    var accel = document.getElementById("accelSetting");
    var reflect = document.getElementById("reflectSetting");
    var bigDial = document.getElementById("bigDial");
    var bigDialt = document.getElementById("bigDialText");
    // callback for large controller
    function changeBigDial(e){
        bigDial.control.resetChaser(null,null,drag.control.value,accel.control.value,reflect.control.value);
        if(accel.control.value === 0 || drag.control.value === 0){
            var str = "Can no move as Drag and/or Acceleration is Zero";
        }else{
            var str  = "A:"+ accel.control.value.toFixed(3);
            str += "D:"+ drag.control.value.toFixed(3);
            str += "R:-"+ reflect.control.value.toFixed(3);
        }
        bigDialt.textContent = str;
    }
    // set callbacks
    drag.control.onchange = changeBigDial;
    accel.control.onchange = changeBigDial;
    reflect.control.onchange = changeBigDial;
    
    // Update all controls
    function update(){
        c.update();
        requestAnimationFrame(update);
    }
    update();
    .testControl {
        width:110px;
        height:110px;
        display: inline-block;
        text-align:center;
    }
    .big {
        width:200px;
        height:200px;
    
    }
    .demo {
        text-align:center;
    }
    <div class="demo"><h3> Examples of variouse Drag and Acceleration settings</h3>
    <p>Click on the control to change the setting. Click drag to adjust setting. The first two rows are preset. <b>D</b> and <b>A</b> above the control are the <b>D</b>rag and <b>A</b>cceleration settings for the control under it.</p>
    <span class="testControl" data-drag="0.1" data-accel="0.9" data-value="0" data-type = "dial" ><b>D</b>:0.1 <b>A</b>:0.9</span>
    <span class="testControl" data-drag="0.2" data-accel="0.8" data-value="0" data-type = "dial" ><b>D</b>:0.2 <b>A</b>:0.8</span>
    <span class="testControl" data-drag="0.3" data-accel="0.7" data-value="0" data-type = "dial" ><b>D</b>:0.3 <b>A</b>:0.7</span>
    <span class="testControl" data-drag="0.4" data-accel="0.6" data-value="0" data-type = "dial" ><b>D</b>:0.4 <b>A</b>:0.6</span>
    <span class="testControl" data-drag="0.5" data-accel="0.5" data-value="0" data-type = "dial" ><b>D</b>:0.5 <b>A</b>:0.5</span>
    <span class="testControl" data-drag="0.6" data-accel="0.4" data-value="0" data-type = "dial" ><b>D</b>:0.6 <b>A</b>:0.4</span>
    <span class="testControl" data-drag="0.7" data-accel="0.3" data-value="0" data-type = "dial" ><b>D</b>:0.7 <b>A</b>:0.3</span>
    <span class="testControl" data-drag="0.8" data-accel="0.2" data-value="0" data-type = "dial" ><b>D</b>:0.8 <b>A</b>:0.2</span>
    <span class="testControl" data-drag="0.9" data-accel="0.1" data-value="0" data-type = "dial" ><b>D</b>:0.9 <b>A</b>:0.1</span><br>
    <span class="testControl" data-drag="0.9" data-accel="0.9" data-value="0" data-type = "dial" ><b>D</b>:0.9 <b>A</b>:0.9</span>
    <span class="testControl" data-drag="0.8" data-accel="0.8" data-value="0" data-type = "dial" ><b>D</b>:0.8 <b>A</b>:0.8</span>
    <span class="testControl" data-drag="0.7" data-accel="0.7" data-value="0" data-type = "dial" ><b>D</b>:0.7 <b>A</b>:0.7</span>
    <span class="testControl" data-drag="0.6" data-accel="0.6" data-value="0" data-type = "dial" ><b>D</b>:0.6 <b>A</b>:0.6</span>
    <span class="testControl" data-drag="0.5" data-accel="0.5" data-value="0" data-type = "dial" ><b>D</b>:0.5 <b>A</b>:0.5</span>
    <span class="testControl" data-drag="0.4" data-accel="0.4" data-value="0" data-type = "dial" ><b>D</b>:0.4 <b>A</b>:0.4</span>
    <span class="testControl" data-drag="0.3" data-accel="0.3" data-value="0" data-type = "dial" ><b>D</b>:0.3 <b>A</b>:0.3</span>
    <span class="testControl" data-drag="0.2" data-accel="0.2" data-value="0" data-type = "dial" ><b>D</b>:0.2 <b>A</b>:0.2</span>
    <span class="testControl" data-drag="0.1" data-accel="0.1" data-value="0" data-type = "dial" ><b>D</b>:0.1 <b>A</b>:0.1</span><br>
    <h3>The following 3 dials control the inertia setting of the large dial</h3>
    <span class="testControl" id="dragSetting" data-value="0" data-display="#display-drag" data-decimals="3" data-min="0" data-max="1"  data-type = "dial" >Drag <span id="display-drag">0.000</span></span>
    <span class="testControl" id="accelSetting" data-value="0" data-display="#display-accel" data-decimals="3" data-min="0" data-max="1" data-type = "dial" >Accel <span id="display-accel">0.000</span></span>
    <span class="testControl" id="reflectSetting" data-value="0" data-display="#display-reflect" data-decimals="3" data-min="0" data-max="1" data-type = "dial" >Reflect <span id="display-reflect">0.000</span></span>
    </div>
    <div class="demo">
    <div class="testControl big" id="bigDial"  data-drag="0.1" data-accel="0.1" data-value="0" data-type = "dial" ><span id="bigDialText">Letf click drag to change</span></div><br>
    </div>


    希望这可以帮助。

    10-08 08:42