微信号:w3cplus_12

介绍:W3CPLUS是一个前端爱好者的家园,W3CPLUS努力打造最优秀的web 前端学习的站点.

使用perspective和transform实现容器上的悬浮效果

2018-03-18 21:29 大漠

特别声明:此篇文章内容来源于@MIHAI IONESCU的《Animate a Container on Mouse Over Using Perspective and Transform》一文。

我一直在做一个网站,这个网站主要用来向用户展示大型图片。我没有创建一个典型的Lightbox效果,而是尝试让一些交互效果更有意思。最后的效果是我在一个容器上添加了一个效果,当用户把鼠标悬浮在图片(用户鼠标移动图片上)时,它会倾斜(鼠标移动到不同的角度时,向不同的方向倾斜)。

最终的效果如下:

这个效果是通过CSS和JavaScript实现的。我想通过这个教程来解释每个部分是如何工作的,这样你就可以很容易地复杂或者扩展它。

在开始之前,我建议你阅读一下perspectivetransfrom相关的教程。在这篇文章中介绍的效果将会使用这些属性,熟悉它们有助于你更好的理解下面的内容。

那我们开始今天的教程学习吧。

设置

首先我们需要在一个容器中放置一个元素。这个容器将有助于我们使用perspective属性。

<div id="container">    <div id="inner"></div>
</div>

为了演示的目的,把卡片放在屏幕的正中:

body {    
   width: 100%;    min-height: 100vh;    display: flex;    justify-content: center;    align-items: center;    margin: 0;    background-color: rgb(220, 220, 220);
}

#container {    
   perspective: 40px;
}

#inner {    
   width: 20em;    height: 18em;    background-color: white;
}

我们创建了一张白色的卡片,它位于屏幕(浅灰色背景)的正中(水平垂直居中于屏幕)。注意,我们给#container设置了perspective的值为40px。在这一点上我们什么也没有做,因为我们没有创建任何transform。这将在稍后的JavaScript中处理。

(function(){    // 初始化    var container = document.getElementById('container'),        inner = document.getElementById('inner'); })()

开始添加JavaScript脚本

下面将是我们需要的一些功能:

var container = document.getElementById('container'),    inner = document.getElementById('inner'); var onMouseEnterHandler = function (event) {    update(event);} var onMouseLeaveHandler = function () {    inner.style = '';} var onMouseMoveHandler = function (event) {    if (isTimeToUpdate()) {        update(event);    } } container.onmouseenter = onMouseEnterHandler;
container.onmouseleave = onMouseLeaveHandler;
container.onmousemove = onMouseMoveHandler;

这些事情是我们要做或者将要做的事情:

  • 事件功能(Handler Functions):这些函数是在事件发生时要处理的事件。我们想要决定当鼠标进入、移动和离开容器时会发生什么,因此每个容器都有一个处理函数

  • 更新功能:我们现在还没有给这个函数添加任何代码,但是它的目的是更新#inner元素的有关于3D旋转相关的样式

  • 更新函数的时间:这是另一个还没有编码的函数,但是当需要更新时它会返回true。这是一种方法,可以减少对update()函数的调用次数,并提高脚本的性能

  • 事件(Event):这是一个JavaScript对象,描述发生的事件

上面的代码做的事情是:

  • 鼠标移入容器#container时,更新div#inner的3D旋转属性

  • 鼠标在容器#container移动时,更新内部div#inner的3D旋转属性

  • 鼠标离开容器#container时,重置div#inner的样式

是时候更新了吗?

让我们添加决定何时更新div#inner的3D旋转。

var counter = 0;
var updateRate = 10;
var isTimeToUpdate = function () {    return counter++ % updateRate === 0; }

当计数器(counter)到达updateRate时,将进行更新。

在这一点上,你可以尝试使用console.log()来替换update函数,并使用updateRate来查看它是如何一起工作的。

鼠标

接下来是鼠标对象。这个比其他部分要更复杂一些。尽管如此,理解起来并不是那么困难,但是代码看起来很吓人,特别是如果你是JavaScript新手的话。

// 初始化

var container = document.getElementById('container'),    inner = document.getElementById('inner');
// 鼠标对象

var mouse = {    _x: 0,    _y: 0,    x: 0,    y: 0,    updatePosition: function (event) {        var e = event || window.event;        
       this.x = e.clientX - this._x;        
       this.y = (e.clientY - this._y) * -1;    },    setOrigin: function (e) {        this._x = e.offsetLeft + Math.floor(e.offsetWidth / 2);        
       this._y = e.offsetTop + Math.floor(e.offsetHeight / 2);    },    show: function () {        return '(' + this.x + ',' + this.y + ')'    } } mouse.setOrigin(container);

让我们一起来讨论这个问题。

  • show():显示鼠标的当前位置

  • setOrigin(e):在element(e)的中心设置鼠标对象的坐标coordinates(0,0)

  • updatePosition():相对于(0,0)更新鼠标对象的当前位置

最后一行代码mouse.setOrigin(container)将鼠标对象的coordinates(0,0)设置在容器的中心。这里有一个例子说明了这一点。

(function(){    
   var container = document.getElementById('container'),        inner = document.getElementById('inner'),        helper = document.getElementById('helper');    console.log(container);    
   var mouse = {        _x: 0,        _y: 0,        x: 0,        y: 0,        updatePosition: function (event) {            var e = event || window.event;            
           this.x = e.clientX - this._x;            
           this.y = (e.clientY - this._y) * -1;        },        setOrigin: function (e) {            this._x = e.offsetLeft + Math.floor(e.offsetWidth / 2);            
           this._y = e.offsetTop + Math.floor(e.offsetHeight / 2);        },        show: function () {            return '(' + this.x + ',' + this.y + ')';        }    }    mouse.setOrigin(container);    
       var counter = 0;    
       var updateRate = 1;    
       var isTimeToUpdate = function () {            return counter ++ % updateRate === 0;        };    
       var onMouseEnterHandler = function (event) {            helper.className = '';            update(event);        }    
       var onMouseLeaveHandler = function () {            inner.style = '';            helper.className = 'hidden';        }    
       var onMouseMoveHandler = function (event) {            if (isTimeToUpdate()) {                update(event);            }            displayMousePositionHelper(event);        }    
       var update = function (event) {            mouse.updatePosition(event);        }    
       var displayMousePositionHelper = function (event) {            var e = event || window.event;            helper.innerHTML = mouse.show();            helper.style = 'top:' + (e.clientY - container.offsetTop) + 'px;left:' + (e.clientX - container.offsetLeft) + 'px;';        }    container.onmouseenter = onMouseEnterHandler;    container.onmouseleave = onMouseLeaveHandler;    container.onmousemove = onMouseMoveHandler; })();

我样想要的是,当你把鼠标移动离容器中心点更远的地方时,那么div#inner旋转的幅度也将更大。

更新鼠标位置的样式

这是update()函数里的代码:

var update = function (event) {    mouse.updatePosition (event);    updateTransformStyle(        (mouse.y / inner.offsetHeight / 2).toFixed(2),        (mouse.x / inner.offsetWidth / 2).toFixed(2)    ) }
var updateTransformStyle = function (x, y) {    var style = 'rotateX(' + x + 'deg) rotateY(' + y + 'deg)';    inner.style.transform = style;    inner.style.webkitTransform = style; }

  • update():更新鼠标位置和更新div#inner的样式

  • updateTransformStyle():更新每个供应商前缀的样式

我们做了什么?

我们最好做些测试!看起来,当鼠标进入并退出卡片时,我们的perspective会发生变化,但它并不像我们想象的那么平滑。

(function(){    
   var container = document.getElementById('container'),        inner = document.ElementBt.Id('inner');    
   var mouse = {        _x: 0,        _y: 0,        x: 0,        y: 0,        updatePosition: function (event) {            var e = event || window.event;            
           this.x = e.clientX - this._x;            
           this.y = (e.clientY - this._y) * -1;        },        setOrigin: function (e) {            this._x = e.offsetLeft + Math.floor(e.offsetWidth / 2);            
           this._y = e.offsetTop + Math.floor(e.offsetHeight / 2);        },        show: function () {            return '(' + this.x + ',' + this.y + ')';        }    };    mouse.setOrigin(container);    
       var counter = 0;    
       var updateRate = 10;    
       var isTimeToUpdate = function () {            return counter++ % updateRate === 0;        };    
       var onMouseEnterHandler = function (event) {            update(event);        };    
       var onMouseLeaveHandler = function () {            inner.style = '';        };    
       var onMouseMoveHandler = function (event) {            if (isTimeToUpdate()) {                update(event);            }        };    
       var update = function (event) {            mouse.updatePosition(event);            updateTransformStyle(                (mouse.y / inner.offsetHeight / 2).toFixed(2),                (mouse.x / inner.offsetWidth / 2).toFixed(2)            );        };    
       var updateTransformStyle = function (x, y) {            var style = 'rotateX(' + x + 'deg) rotateY(' + y + 'deg)';            inner.style.transform = style;            inner.style.webkitTransform = style;        }    container.onmouseenter = onMouseEnterHandler;    container.onmouseleave = onMouseLeaveHandler;    container.onmousemove = onMouseMouveHandler; })();


每次计数器(counter)刷新updateRate时更新div#inner的旋转值。这在更新之间产生了迟钝的transform效果。

如何破这个局呢?可以使用CSS的transition

添加transition效果

#inner {    transition: transform 0.5s;}

这些都是任意数字。你可以使用perspectivetransform的值,使效果或多或少地达到你认为合适的效果。

注意,调整页面大小会导致一些问题,因为页面中容器的位置发生了变化。解决方案是在页面大小调整后将鼠标对象(mouse)重新放到容器(container)中。

CSS 自定义属性

到整理这文章之时,CSS的自定义属性(CSS变量)得到了众多浏览器的支持,对于文章开头演示的效果,我们可以尝试使用CSS的自定义属性来实现,这将会让事情变得更为简单。这里来看一个简单的示例。

使用CSS自定义变量,需要先声明相关的CSS变量,这里我们需要控制的是鼠标xy的坐标,所以我们先在:root中声明对应的两个变量名,并且设置其默认值为0

:root {    
   --mouseX: 0deg;    --mouseY: 0deg;
}

和前面示例一样,在#container容器中设置perspective的值,这个值根据自己想要的效果可以进行调整,另外在这个容器中使用perspective-origin设置perspective的中心点为容器的中心。与前面示例不同的是,#inner是的transform是通过JavaScript对应的鼠标事件来控制,而我们的示例中在样式中调用CSS的自定义变量,然后再通过JavaScript的.setProperty()来控制调用的变量值。先来看CSS部分的代码:

#inner {    
   transform: rotateX(calc(var(--mouseY)))    rotateY(calc(var(--mouseX)));    transition-origin: center;    transition: transform 0.5s;    box-shadow: 2px 2px 50px rgba(0, 0, 0, 0.2);}

添加对应的几个JavaScript对应的鼠标事件,相比之下,这个代码变得更简单多了,代码量也少了:

(function(){    
   var container = document.getElementById('container'),        inner = document.getElementById('inner');    
   var counter = 0;    
   var refreshRate = 10;    
   var isTimeToUpdate = function() {        
       return counter++ % refreshRate === 0;    };    
   var updateTransformStyle = function (xPos, yPos) {        var wh = window.innerHeight / 2,            ww = window.innerWidth / 2;        inner.style.setProperty('--mouseX', (xPos - ww) / 300 + 'deg');        inner.style.setProperty('--mouseY', (yPos - wh) / 300 + 'deg');    }    
   var onMouseEnterHandler = function(event) {        updateTransformStyle(event.clientX,event.clientY);    };    
   var onMouseMoveHandler = function (event) {        if (isTimeToUpdate()) {            updateTransformStyle(event.clientX,event.clientY);        }    }    
   var onMouseLeaveHandler = function() {        inner.style = "";    };    
   var onTouchMoveHander = function (event) {        event.preventDefault();        
       var touch = event.targetTouches[0];        
       if (touch) {            updateTransformStyle(touch.pageX, touch.pageY);        }    }    container.onmousemove = onMouseMoveHandler;    container.ontouchmove = onTouchMoveHander;    container.onmouseleave = onMouseLeaveHandler; })()

最终的效果如下:

总结

现在我们完成了想要的效果。我们有了一个容器来让元素变得更具交互性。本文开头演示的效果中加入了图片,但是除了图像之外,它还可以用于其他任何你想要的东西,包括表单,Modal弹出框或者你想要的其他任何内容。感兴的同学,不仿试试!


文章涉及到图片和代码,如果展示不全给您带来不好的阅读体验,欢迎点击文章底部的 阅读全文。如果您觉得小站的内容对您的工作或学习有所帮助,欢迎关注此公众号。





W3cplus.com

————————————

记述前端那些事,引领web前沿


长按二维码,关注W3cplus



 
W3cplus 更多文章 CSS Houdini和CSS Paint API 中国第四届CSS开发者大会来啦,赶紧抢票,相约厦门 分享手淘过年项目中采用到的前端技术 CSS Tips:段落每行渐变色文本效果 Vue 2.0学习笔记:组件数据传递
猜您喜欢 软件测试从零开始(二) AndroidTShare Weekly No.15 Eclipse开发环境配置 如何解决容器网络性能及复杂网络部署问题? 【第2章第149回】深入浅出ES6(十一):生成器 Generators,续篇