JavaScript中的文本拖影效果 [英] Text smear effect in JavaScript

查看:216
本文介绍了JavaScript中的文本拖影效果的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经验丰富的Javascript,但没有深入其先进的图形功能(画布,webGL,three.js等)。我想创建一个像这一个的失真效果,除了我想将其应用于文本而不是图片。基本上我想有一些看起来像纯HTML的文本,但当用户将鼠标悬停在其上时,文本应该弯曲/ warp / smear响应。



到目前为止,我发现了两个类似但不完全是我想要的SO帖子:第一个太简单,因为我想扭曲和弯曲文本,而不只是向下移动页面。 更有趣,因为我有一个预感,我需要使用像Three.js这样的库来实现这个效果,但我想要的是2d,而不是3d,我想实际扭曲文本的形状,而不是围绕轴旋转。



我想知道如何创建这个效果,是否有一个名字为我想要的特定效果(已经有麻烦找到好的例子在线),任何好的例子,建议,任何真的。提前感谢!

解决方案

很多可能性。



这是一个简单的WEBGL 2D纹理绘制到标准2D画布的例子。有一些样板的鼠标,画布,webGL,所以你必须把它分开自己。



FX是在Fragment Shader。而不是移动纹理坐标我只是映射了一个2D矢量字段在图像上(几乎随机地做了它,我去)矢量偏移从像素查找纹理。通过鼠标上下控制的控制FX和鼠标的量从左向右移动 Phase



将鼠标移动到图像顶部可减少效果量。



底部的函数 webGLRender



设置片段着色器值,并渲染webGl然后2D上下文 drawImage 来渲染以显示画布。 Fragment着色器在上面。



由于webGL图像是通过ctx.draw2D渲染的,它很容易调整大小,使得webGL渲染整个显示分辨率独立。如果您遇到性能问题(百万像素* 4 +范围内的图片),您可以减少输入图片大小



WebGL无法呈现不属于同一域的图片(tained)不像2D画布webGL需要访问像素数据来绘制纹理,因此保存图像将使它抛出安全错误。



  // boiler plateconst U = undefined; const RESIZE_DEBOUNCE_TIME = 100; var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime = 0,resizeCount = 0; var L = typeof log ===function? log:function(d){console.log(d); } createCanvas = function(){var c,cs; cs =(c = document.createElement(canvas))。 cs.position =absolute; cs.top = cs.left =0px; cs.zIndex = 1000; document.body.appendChild(c); return c;} resizeCanvas = function(){if(canvas === U){canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext(2d); if(typeof setGlobals ===function){setGlobals(); } if(typeof onResize ===function){resizeCount + = 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}} function debounceResize(){resizeCount  -  = 1; if(resizeCount <= 0){onResize();}} setGlobals = function(){cw =(w = canvas.width)/ 2; ch =(h = canvas.height)/ 2; mouse.updateBounds(); } mouse =(function(){function preventDefault(e){e.preventDefault();} var mouse = {x:0,y:0,w:0,alt:false,shift:false,ctrl:false,buttonRaw :0,over:false,bm:[1,2,4,6,5,3],active:false,bounds:null,crashRecover:null,mouseEvents:mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel, DOMMouseScroll.split(,)}; var m = mouse; function mouseMove(e){var t = e.type; mx = e.clientX  -  m.bounds.left; my = e.clientY  -  m.bounds .top; 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.buttonRaw = 0; m.over = false;} else if(t ===mouseover){m.over = true;} else if(t ===mousewheel){mw = e.wheelDelta;} else if (t ===DOMMouseScroll){mw = -e.detail;} if(m.callbacks){m.callbacks.forEach(c => c(e)); } if((m.buttonRaw& 2)&& m.crashRecover!== null){if(typeof m.crashRecover ===function){setTimeout(m.crashRecover,0);}} e .preventDefault(); } m.updateBounds = function(){if(m.active){m.bounds = m.element.getBoundingClientRect(); }} m.addCallback = function(callback){if(typeof callback ===function){if(m.callbacks === U){m.callbacks = [callback]; } else {m.callbacks.push(callback); }} else {throw new TypeError(mouse.addCallback argument must be a function); }} m.start = function(element,blockContextMenu){if(m.element!== U){m.removeMouse(); } m.element = element === U? document:element; m.blockContextMenu = blockContextMenu === U? false:blockContextMenu; m.mouseEvents.forEach(n => {m.element.addEventListener(n,mouseMove);}); if(m.blockContextMenu === true){m.element.addEventListener(contextmenu,preventDefault,false); } m.active = true; m.updateBounds(); } m.remove = function(){if(m.element!== U){m.mouseEvents.forEach(n => {m.element.removeEventListener(n,mouseMove);}); if(m.contextMenuBlocked === true){m.element.removeEventListener(contextmenu,preventDefault);} m.element = m.callbacks = m.contextMenuBlocked = U; m.active = false; }} return mouse;})(); resizeCanvas(); mouse.start(canvas,true); window.addEventListener(resize,resizeCanvas); function display(){ctx.setTransform(1,0,0,1,0,0); // reset transform ctx.globalAlpha = 1; // reset alpha ctx.clearRect(0,0,w,h); if(webGL!== undefined){webGLRender(); }} function update(timer){//主更新循环globalTime = timer;显示(); //调用演示代码requestAnimationFrame(update);} requestAnimationFrame(update); var globalTime = new Date()。valueOf(); // global to this //创建顶点和片段着色器function createProgramFromScripts(gl,ids){var shaders = []; for(var i = 0; i  


I'm pretty experienced with Javascript but haven't delved much into its advanced graphics capabilities (canvas, webGL, three.js, etc). I want to create a distortion effect kind of like this one, except I'd like to apply it to text instead of an image. Basically I want to have some text that looks like plain HTML at first glance but when the user mouses over it, the text should bend/warp/smear in response.

So far I've found two SO posts that are similar but not exactly what I want: the first is too simple, as I want to warp and bend the text, not just shift it down the page. The second is more interesting, as I have a hunch I'll need to use a library like Three.js to achieve this effect, but I want something 2d, not 3d, and I want to actually warp the "shape" of the text, not just spin it around an axis.

I'm wondering how to create this effect, whether there is a name for the specific effect I want (have had trouble finding good examples online), any good examples, advice, anything really. Thanks in advance!

解决方案

Many possibilities.

Here is an example of a simple WEBGL 2D texture drawn onto a standard 2D canvas. There is a bit of boilerplate for mouse,canvas,webGL so you will have to pick it apart yourself.

The FX is in the Fragment Shader. Rather than move the texture coords I just mapped a 2D vector field over the image (pretty much randomly made it up as i went) The vectors offset the pixel lookup from the texture. The amount controlled by mouse up and down controls the amount of the FX and the mouse from left to right moves the Phase setting.

Moving mouse to the top of the image reduces the effect amount. Bottom right is at max.

.

The function at the bottom webGLRender sets the fragment shader values and renders the webGl then 2D context drawImage to render to display canvas. The Fragment shader is above that.

As the webGL image is rendered via ctx.draw2D it is easy to resize making the webGL render total display resolution independent. If you have performance issues (image in the mega pixel * 4 + range) you can reduce the input image size

WebGL can not render images that are not from the same domain (tained) unlike 2D canvas webGL requires access to the pixel data to draw textures and thus tained images will make it throw security errors. I have used a 2d canvas rather than an image as I could not find an image the would not taint the canvas.

// boiler plate
const U = undefined;const RESIZE_DEBOUNCE_TIME = 100;
var w,h,cw,ch,canvas,ctx,mouse,createCanvas,resizeCanvas,setGlobals,globalTime=0,resizeCount = 0; 
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () { var c,cs; cs = (c = document.createElement("canvas")).style; cs.position = "absolute"; cs.top = cs.left = "0px"; cs.zIndex = 1000; document.body.appendChild(c); return c;}
resizeCanvas = function () {
    if (canvas === U) { canvas = createCanvas(); } canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx = canvas.getContext("2d"); 
    if (typeof setGlobals === "function") { setGlobals(); } if (typeof onResize === "function"){ resizeCount += 1; setTimeout(debounceResize,RESIZE_DEBOUNCE_TIME);}
}
function debounceResize(){ resizeCount -= 1; if(resizeCount <= 0){ onResize();}}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; mouse.updateBounds(); }
mouse = (function(){
    function preventDefault(e) { e.preventDefault(); }
    var mouse = {
        x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0, over : false, bm : [1, 2, 4, 6, 5, 3], 
        active : false,bounds : null, crashRecover : null, mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
    };
    var m = mouse;
    function mouseMove(e) {
        var t = e.type;
        m.x = e.clientX - m.bounds.left; m.y = e.clientY - m.bounds.top;
        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.buttonRaw = 0; 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 (m.callbacks) { m.callbacks.forEach(c => c(e)); }
        if((m.buttonRaw & 2) && m.crashRecover !== null){ if(typeof m.crashRecover === "function"){ setTimeout(m.crashRecover,0);}}        
        e.preventDefault();
    }
    m.updateBounds = function(){
        if(m.active){
            m.bounds = m.element.getBoundingClientRect();
        }
    }
    m.addCallback = function (callback) {
        if (typeof callback === "function") {
            if (m.callbacks === U) { m.callbacks = [callback]; }
            else { m.callbacks.push(callback); }
        } else { throw new TypeError("mouse.addCallback argument must be a function"); }
    }
    m.start = function (element, blockContextMenu) {
        if (m.element !== U) { m.removeMouse(); }        
        m.element = element === U ? document : element;
        m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
        m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
        if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
        m.active = true;
        m.updateBounds();
    }
    m.remove = function () {
        if (m.element !== U) {
            m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
            if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
            m.element = m.callbacks = m.contextMenuBlocked = U;
            m.active = false;
        }
    }
    return mouse;
})();

resizeCanvas(); 
mouse.start(canvas,true); 
window.addEventListener("resize",resizeCanvas); 
function display(){ 
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.clearRect(0,0,w,h);
    if(webGL !== undefined){
        webGLRender();
    }
}
function update(timer){ // Main update loop
    globalTime = timer;
    display();  // call demo code
    requestAnimationFrame(update);
}
requestAnimationFrame(update);
var globalTime = new Date().valueOf();  // global to this 

// creates vertex and fragment shaders 
function createProgramFromScripts( gl, ids) {
    var shaders = [];
    for (var i = 0; i < ids.length; i += 1) {
        var script = shadersSource[ids[i]];
        if (script !== undefined) {
            var shader = gl.createShader(gl[script.type]);
            gl.shaderSource(shader, script.source);
            gl.compileShader(shader);
            shaders.push(shader);  
        }else{
            throw new ReferenceError("*** Error: unknown script ID : " + ids[i]);
        }
    }
    var program = gl.createProgram();
    shaders.forEach((shader) => {  gl.attachShader(program, shader); });
    gl.linkProgram(program);
    return program;    
}

// setup simple 2D webGL image processor
var webGL;
function startWebGL(image) {
  // Get A WebGL context
  webGL = document.createElement("canvas");
  webGL.width = image.width;
  webGL.height = image.height;
  webGL.gl = webGL.getContext("webgl");
  var gl = webGL.gl;
  var program = createProgramFromScripts(gl, ["VertexShader", "FragmentShader"]);
  gl.useProgram(program);
  var positionLocation = gl.getAttribLocation(program, "a_position");
  var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
  var texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.0,  0.0,1.0,  0.0,0.0,  1.0,0.0,  1.0,1.0,  0.0,1.0,  1.0]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(texCoordLocation);
  gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  var resolutionLocation = gl.getUniformLocation(program, "u_resolution");

  // lookup uniforms for frag shader
  var locs = {}
  locs.timer = gl.getUniformLocation(program, "time");  // the time used to control waves
  locs.phase = gl.getUniformLocation(program, "phase"); // Sort of phase, moves to attractors around
  locs.amount = gl.getUniformLocation(program, "amount"); // Mix amount of effect and flat image
  webGL.locs = locs;
  
  gl.uniform2f(resolutionLocation, webGL.width, webGL.height);
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
  setRectangle(gl, 0, 0, image.width, image.height);
}
function setRectangle(gl, x, y, width, height) {
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     x1, y1,
     x2, y1,
     x1, y2,
     x1, y2,
     x2, y1,
     x2, y2]), gl.STATIC_DRAW);
}

function randomInt(range) {
  return Math.floor(Math.random() * range);
}



var shadersSource = {
    VertexShader : {
        type : "VERTEX_SHADER",
        source : `
            attribute vec2 a_position;
            attribute vec2 a_texCoord;
            uniform vec2 u_resolution;
            varying vec2 v_texCoord;
            void main() {
                vec2 zeroToOne = a_position / u_resolution;
                vec2 zeroToTwo = zeroToOne * 2.0;
                vec2 clipSpace = zeroToTwo - 1.0;
                gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
                v_texCoord = a_texCoord;
            }`
    },
    FragmentShader : {
        type : "FRAGMENT_SHADER",
        source : `
            precision mediump float;
            uniform sampler2D u_image;
            uniform float time;
            uniform float phase;
            uniform float amount;
            varying vec2 v_texCoord;
            vec2 offset;
            float dist;
            float edge;
            float v;
            vec2 pos1 = vec2(0.5 + sin(phase * 0.03)*1.3, 0.5 + cos(phase * 0.032)*1.3);
            vec2 pos2 = vec2(0.5 + cos(phase * 0.013)*1.3,0.5 + cos(phase*0.012)*1.3);
            void main() {
                dist = distance(pos1,v_texCoord) * distance(pos2,v_texCoord);

               
                edge = 1. - distance(vec2(0.5,0.5),v_texCoord) / 0.707;
                v = time * dist * 0.0001 * edge * phase;
                offset = vec2(
                        v_texCoord.x + sin(v+time) * 0.1 * edge * amount,
                        v_texCoord.y + cos(v+time) * 0.1 * edge * amount
                );
                //offset = smoothstep(v_texCoord.x,offset.x,abs(0.5-v_textCoord.x) );
                gl_FragColor = texture2D(u_image, offset);
            }`
    }
}


var md = 0;
var mr = 0;
var mdy = 0;
var mry = 0;
function webGLRender(){
    var gl = webGL.gl;
    md += (mouse.x / canvas.width - mr) * 0.16;
    md *= 0.18;
    mr += md;  
    mdy += (mouse.y - mry) * 0.16;
    mdy *= 0.18;
    mry += mdy;
    gl.uniform1f(webGL.locs.timer, globalTime/100);
    gl.uniform1f(webGL.locs.phase, mr * 400);
    gl.uniform1f(webGL.locs.amount, ((mry/canvas.height)) * 9);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
    ctx.drawImage(webGL,0,0, canvas.width, canvas.height);
}

var image = document.createElement("canvas");
image.width = 1024;
image.height = 512;
image.ctx = image.getContext("2d");
image.ctx.font = "192px Arial";
image.ctx.textAlign = "center";
image.ctx.textBaseline = "middle";
image.ctx.lineJoin = "round";
image.ctx.lineWidth = 32;
image.ctx.strokeStyle = "red";
image.ctx.fillStyle = "black";
image.ctx.strokeText("WOBBLE",512,256);
image.ctx.lineWidth = 16;
image.ctx.strokeStyle = "white";

image.ctx.strokeText("WOBBLE",512,256);
image.ctx.fillText("WOBBLE",512,256);
image.ctx.font = "32px Arial";
image.ctx.fillText("Mouse position on image controls wobble",512,32);
image.ctx.fillText("Using WebGL and 2D Canvas",512,512-32);

startWebGL(image);
/*var image = new Image(); // load image
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";  // MUST BE SAME DOMAIN!!!
image.onload = function() {
    startWebGL(image);
}*/

这篇关于JavaScript中的文本拖影效果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆