实用小技巧:用CSS和JavaScript就能实现的海报滤镜效果

本文介绍了一种使用CSS和JavaScript实现的图像创意效果。该效果通过裁剪背景图像的小片段并将其放置在不同位置来创建有趣的外观。同时介绍了如何通过视差效果增加互动性和视觉吸引力。

http://igeekbar.com/igeekbar/post/243.htm

2a068a3c-3ae6-42df-9533-7ec7a08cc436.jpg

 

 

 

 

今天我们想告诉你如何通过图像来实现一个非常简单而有趣的效果。本问的灵感来自poster of the Grand Canyon,图像的一些小片段被裁剪掉并放置在不同的位置,创造出一种有趣和创造性的外观。今天我们将向你展示如何使用CSS和一些JavaScript创建类似的效果。

 

一般的想法是创建一个具有背景图像的分区,然后动态添加几个新的分区。这些分区的子divs将被移动,另一个内部div将有一个剪辑路径,只显示图像的一小部分。为了让海报更有趣,我们希望可以选择视差(或倾斜)效果。这就是为什么我们将使用两个嵌套分区。

 

赶紧开始吧!

 

cc116ceb-58a4-48e8-a25d-5a91a62f1873.jpg

 

查看效果图

 

 

小片段标记

 

为了创建动态片段,只需要一个带有背景图像的简单的div:

<div class="fragment-wrap" style="background-image: url(img/1.jpg)"></div>

 

脚本创建如下:

 

<div class="fragment-wrap" style="background-image: url(img/1.jpg)">
	<div class="fragment">
		<div class="fragment__piece"></div>
	</div>
	<div class="fragment">
		<div class="fragment__piece"></div>
	</div>
	<!-- ... -->
</div>

 

我们的脚本仍然需要为div添加一些单独的样式属性,但是让我们先来看一下常见的样式。

 

70768620-f71a-467d-8d0a-fe36078d4d29.jpg

 

The Styles

 

主图像div,片段包装需要宽度和高度以及一些边距,以便它在我们的布局中正确定位。为了使图像响应,我们将使用相对的视口单位。由于我们需要一个替代布局,我们还将编写一个修饰符类来将图像放在右侧:

 

.fragment-wrap {
	width: 30vw;
	height: 80vh;
	min-height: 550px;
	max-width: 800px;
	max-height: 1000px;
	position: relative;
	margin: 0 30vw 0 0;
}
.fragment-wrap--right {
	margin: 0 0 0 30vw;
}

 

该片段和fragment__piece分区将是绝对的位置,并占据所有可用的宽度和高度。我们将动态地应用剪辑路径到这个div,所以现在还没有其他的需要添加:

.fragment,
.fragment__piece {
	position: absolute;
	width: 100%;
	height: 100%;
	top: 0;
	left: 0;
	pointer-events: none;
}

 

对于视差情况,我们将设置到片段 div 的位移:

 

.fragment {
	transition: transform 0.2s ease-out;
}

 

我们也将在父背景图像中应用到它。对于两个div,我们设置以下背景图像属性:

.fragment-wrap,
.fragment__piece {
	background-size: cover;
	background-repeat: no-repeat;
	background-position: 50% 0%;
}

 

这些都是我们需要的元素的常见样式。如果我们没有JS可用,图像就会被简单地显示出来,而没有很少的片段效果。

 

现在我们来编写效果功能。

 

 

JavaScript

 

对于这种效果的功能,我们将做一个小插件。让我们来看看选项:

FragmentsFx.prototype.options = {
	// Number of fragments.
	fragments: 25, 
	// The boundaries of the fragment translation (pixel values).
	boundaries: {x1: 100, x2: 100, y1: 50, y2: 50},
	// The area of the fragments in percentage values (clip-path).
	// We can also use random values by setting options.area to "random".
	area: 'random',
	/* example with 4 fragments (percentage values)
	[{top: 80, left: 10, width: 3, height: 20},{top: 2, left: 2, width: 4, height: 40},{top: 30, left: 60, width: 3, height: 60},{top: 10, left: 20, width: 50, height: 6}]
	*/
	// If using area:"random", we can define the area´s minimum and maximum values for the clip-path. (percentage values)
	randomIntervals: {
		top: {min: 0,max: 90},
		left: {min: 0,max: 90},
		// Either the width or the height will be selected with a fixed value (+- 0.1) for the other dimension (percentage values).
		dimension: {
			width: {min: 10,max: 60, fixedHeight: 1.1},
			height: {min: 10,max: 60, fixedWidth: 1.1}
		}
	},
	parallax: false,
	// Range of movement for the parallax effect (pixel values).
	randomParallax: {min: 10, max: 150}
};

 

了解如何使用randomIntervals和维度的最佳方式是查看演示示例。我们采用五种不同的方式,视觉效果显示出它们的不同之处。

首先要做的是从我们的片段包装元素构建布局,并创建我们前面提到的结构:

FragmentsFx.prototype._init = function() {
	// The dimensions of the main element.
	this.dimensions = {width: this.el.offsetWidth, height: this.el.offsetHeight};
	// The source of the main image.
	this.imgsrc = this.el.style.backgroundImage.replace('url(','').replace(')','').replace(/\"/gi, "");;
	// Render all the fragments defined in the options.
	this._layout();
	// Init/Bind events
	this._initEvents();
};

 

我们将创建在选项中指定的片段元素数量:

FragmentsFx.prototype._layout = function() {
	// Create the fragments and add them to the DOM (append it to the main element).
	this.fragments = [];
	for (var i = 0, len = this.options.fragments; i < len; ++i) {
		const fragment = this._createFragment(i);
		this.fragments.push(fragment);
	}
};
FragmentsFx.prototype._createFragment = function(pos) {
	var fragment = document.createElement('div');
	fragment.className = 'fragment';
	// Set up a random number for the translation of the fragment when using parallax (mousemove).
	if( this.options.parallax ) {
		fragment.setAttribute('data-parallax', getRandom(this.options.randomParallax.min,this.options.randomParallax.max));
	}
	// Create the fragment "piece" on which we define the clip-path configuration and the background image.
	var piece = document.createElement('div');
	piece.style.backgroundImage = 'url(' + this.imgsrc + ')';
	piece.className = 'fragment__piece';
	piece.style.backgroundImage = 'url(' + this.imgsrc + ')';
	this._positionFragment(pos, piece);
	fragment.appendChild(piece);
	this.el.appendChild(fragment);
	
	return fragment;
};

 

对于设置translations和clip-path属性(如果支持的话);如果不支持,我们使用clip:rect()),我们从选项中取出我们定义的值。Translations 总是随机的,但我们确实需要确保片段保留在预定义的边界内。剪辑路径可以是随机的(在定义的间隔内)或明确设置。

 

FragmentsFx.prototype._positionFragment = function(pos, piece) {
	const isRandom = this.options.area === 'random',
		  data = this.options.area[pos],
		  top = isRandom ? getRandom(this.options.randomIntervals.top.min,this.options.randomIntervals.top.max) : data.top,
		  left = isRandom ? getRandom(this.options.randomIntervals.left.min,this.options.randomIntervals.left.max) : data.left;
	// Select either the width or the height with a fixed value for the other dimension.
	var width, height;
	if( isRandom ) {
		if(!!Math.round(getRandom(0,1))) {
			width = getRandom(this.options.randomIntervals.dimension.width.min,this.options.randomIntervals.dimension.width.max);
			height = getRandom(Math.max(this.options.randomIntervals.dimension.width.fixedHeight-0.1,0.1), this.options.randomIntervals.dimension.width.fixedHeight+0.1);
		}
		else {
			height = getRandom(this.options.randomIntervals.dimension.width.min,this.options.randomIntervals.dimension.width.max);
			width = getRandom(Math.max(this.options.randomIntervals.dimension.height.fixedWidth-0.1,0.1), this.options.randomIntervals.dimension.height.fixedWidth+0.1);
		}
	}
	else {
		width = data.width;
		height = data.height;
	}
	if( !isClipPathSupported ) {
		const clipTop = top/100 * this.dimensions.height,
			  clipLeft = left/100 * this.dimensions.width,
			  clipRight = width/100 * this.dimensions.width + clipLeft,
			  clipBottom = height/100 * this.dimensions.height + clipTop;
		piece.style.clip = 'rect(' + clipTop + 'px,' + clipRight + 'px,' + clipBottom + 'px,' + clipLeft + 'px)';
	}
	else {
		piece.style.WebkitClipPath = piece.style.clipPath = 'polygon(' + left + '% ' + top + '%, ' + (left + width) + '% ' + top + '%, ' + (left + width) + '% ' + (top + height) + '%, ' + left + '% ' + (top + height) + '%)';
	}
	// Translate the piece.
	// The translation has to respect the boundaries defined in the options.
	const translation = {
			x: getRandom(-1 * left/100 * this.dimensions.width - this.options.boundaries.x1, this.dimensions.width - left/100 * this.dimensions.width + this.options.boundaries.x2 - width/100 * this.dimensions.width),
			y: getRandom(-1 * top/100 * this.dimensions.height - this.options.boundaries.y1, this.dimensions.height - top/100 * this.dimensions.height + this.options.boundaries.y2 - height/100 * this.dimensions.height)
		  };
	piece.style.WebkitTransform = piece.style.transform = 'translate3d(' + translation.x + 'px,' + translation.y +'px,0)';
};

 

当我们调整窗口大小时,元素的尺寸可能会改变,所以我们要确保所有内容都被调整。为了保持简单,我们再次重新计算一下,这意味着我们将做一个新的布局。

如果视差选项为真,我们希望遵循鼠标位置(如果我们将鼠标悬停在元素上)并在选项中定义的范围内翻译片段。如果我们离开元素,我们希望碎片能够移回原来的位置。

FragmentsFx.prototype._initEvents = function() {
	const self = this;
	// Parallax movement.
	if( this.options.parallax ) {
		this.mousemoveFn = function(ev) {
			requestAnimationFrame(function() {
				// Mouse position relative to the document.
				const mousepos = getMousePos(ev),
					// Document scrolls.
					docScrolls = {left : document.body.scrollLeft + document.documentElement.scrollLeft, top : document.body.scrollTop + document.documentElement.scrollTop},
					bounds = self.el.getBoundingClientRect(),
					// Mouse position relative to the main element (this.el).
					relmousepos = { x : mousepos.x - bounds.left - docScrolls.left, y : mousepos.y - bounds.top - docScrolls.top };
				// Movement settings for the animatable elements.
				for(var i = 0, len = self.fragments.length; i <= len-1; ++i) {
					const fragment = self.fragments[i],
						t = fragment.getAttribute('data-parallax'),
						transX = t/(self.dimensions.width)*relmousepos.x - t/2,
						transY = t/(self.dimensions.height)*relmousepos.y - t/2;
						fragment.style.transform = fragment.style.WebkitTransform = 'translate3d(' + transX + 'px,' + transY + 'px,0)';
				}
			});
		};
		this.el.addEventListener('mousemove', this.mousemoveFn);
		this.mouseleaveFn = function(ev) {
			requestAnimationFrame(function() {
				// Movement settings for the animatable elements.
				for(var i = 0, len = self.fragments.length; i <= len-1; ++i) {
					const fragment = self.fragments[i];
					fragment.style.transform = fragment.style.WebkitTransform = 'translate3d(0,0,0)';
				}
			});
		};
		this.el.addEventListener('mouseleave', this.mouseleaveFn);
	}
	// Window resize - Recalculate clip values and translations.
	this.debounceResize = debounce(function(ev) {
		// total elements/configuration
		const areasTotal = self.options.area.length;
		// Recalculate dimensions.
		self.dimensions = {width: self.el.offsetWidth, height: self.el.offsetHeight};
		// recalculate the clip/clip-path and translations
		for(var i = 0, len = self.fragments.length; i <= len-1; ++i) {
			self._positionFragment(i, self.fragments[i].querySelector('.fragment__piece'));
		}
	}, 10);
	window.addEventListener('resize', this.debounceResize);
};

 

就这样!看看演示,看一些例子。感谢你的阅读,我们希望你喜欢这个小教程!

转载于:https://my.oschina.net/u/3599285/blog/1305472

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值