小片段:创建简单的图像海报效果

LittleFragments_800x600

Today we’d like to show you how to achieve a very simple, yet interesting effect with an image. The inspiration comes from a poster of the Grand Canyon with a fun distortion-like effect: some pieces of the image are cut out and placed in a different position. The pieces are very small which creates an interesting and creative look. We’ll be showing you today how to create a similar effect with CSS and some JavaScript.

今天,我们想向您展示如何用图像实现非常简单但有趣的效果。 灵感来自大峡谷海报,具有有趣的扭曲效果:将图像的某些部分切出并放置在不同的位置。 作品很小,营造出有趣而富有创意的外观。 今天我们将向您展示如何使用CSS和一些JavaScript创建类似的效果。

The general idea is to create a division that has a background image and then add several new divisions dynamically. Each one of those child divs will be translated and another inner div will have a clip-path, showing only a tiny part of the image. To add some more fanciness, we want it to be possible to have an option parallax (or tilt) effect. That’s why we’ll be using two nested divisions.

总体思路是创建一个具有背景图像的分区,然后动态添加几个新的分区。 这些子div中的每个子div都将被翻译,另一个内部div将具有剪切路径,仅显示图像的一小部分。 为了增加一些新颖性,我们希望它可以具有视差(或倾斜)效果。 这就是为什么我们将使用两个嵌套的分区。

Let’s get started!

让我们开始吧!

“小片段”标记 (The “Little Fragments” Markup)

What we need initially in order to create our dynamic fragments is just a simple div with a background image:

为了创建动态片段,我们最初需要的只是带有背景图像的简单div:

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

What we want our script to then create is the following:

我们希望脚本创建以下内容:

<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>

Our script will still need to add some individual style properties to the divs but let’s first have a look at the common styles.

我们的脚本仍将需要向div中添加一些单独的样式属性,但让我们首先看一下常见样式。

LittleFragments_01

风格 (The Styles)

The main image div, the fragment-wrap needs a width and a height and some margin just so that it is positioned correctly in our layout. To make the image responsive, we’ll be using relative viewport units. Since we’ll want an alternate layout, we’ll also write a modifier class to put the image more to the right side:

主图像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;
}

The fragment and fragment__piece divisions will be of absolute position and occupy all available width and height. We will apply a clip-path and a translation to this div dynamically, so nothing else needs to be added at this point:

fragmentfragment__piece划分将处于绝对位置,并占据所有可用的宽度和高度。 我们将向此div动态应用剪切路径和转换,因此,此时无需添加其他任何内容:

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

For the parallax case, we’ll set a transition to the fragment div:

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

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

We will also apply the parent’s background image to it. For both divs we set the following background image properties:

我们还将对其应用父级的背景图像。 对于两个div,我们都设置了以下背景图片属性:

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

And those are all the common styles we need for the elements. If we don’t have JS available, the image gets simply shown without the little fragments effect.

这些都是我们需要的元素通用样式。 如果我们没有JS,则图像会简单显示而不会产生小碎片效果。

Let’s now code up the effect functionality.

现在让我们编写效果功能的代码。

JavaScript (The JavaScript)

For the functionality of this effect, we’ll make a little plugin. Let’s have a look at the options:

为了实现这种效果的功能,我们将制作一个小插件。 让我们看一下这些选项:

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}
};

The best way to understand how the randomIntervals and the dimensions can be used, is to have a look at the demo examples. There are five different ways we employ those and the visual result shows how they differ.

要了解如何使用randomIntervals维度,最好的方法是看一下演示示例。 我们采用了五种不同的方式,视觉结果表明了它们的不同之处。

First thing to do is to build the layout from our fragment-wrap element and create the structure we mentioned earlier:

首先要做的是从fragment-wrap元素构建布局并创建我们前面提到的结构:

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();
};

We are going to create the amount of fragment elements specified in the options:

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

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;
};

For setting the translations and the clip-path property (if supported; if not we use clip: rect()) we take our defined values from the options. The translations are always random, but we do need to make sure that the fragment pieces stay within the predefined boundaries. The clip-path can be either random (within the interval defined) or set explicitly.

为了设置转换和clip-path属性(如果支持,如果不使用,我们使用clip:rect()),我们从选项中获取定义的值。 翻译总是随机的,但是我们需要确保片段保持在预定义的边界内。 剪切路径可以是随机的(在定义的间隔内),也可以是明确设置的。

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)';
};

When we resize the window, the element’s dimensions might change so we want to make sure that everything is adjusted. To keep it simple we recalculate everything again, meaning that we’ll make a new layout.

调整窗口大小时,元素的尺寸可能会更改,因此我们要确保所有内容都已调整。 为简单起见,我们再次重新计算所有内容,这意味着我们将进行新的布局。

If the parallax option is true, we want to follow the mouse position (if we are hovering the element) and translate the fragments in the range defined in the options. If we leave the element, we want that the fragments move back to their original positions.

如果视差选项为true,则我们要跟随鼠标的位置(如果我们将鼠标悬停在元素上)并在选项中定义的范围内平移片段。 如果我们离开元素,我们希望片段移回其原始位置。

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);
};

And that’s all! Check out the demo to see some examples. Thank you for reading and we hope you enjoyed this little tutorial!

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

Browser Support: 浏览器支持:
  • ChromeSupported

    支援Chrome

  • FirefoxSupported

    Firefox支持

  • Internet ExplorerSupported from version E

    版本E支持Internet Explorer

  • SafariSupported

    Safari支持

  • OperaSupported

    歌剧支持

参考和鸣谢(References and Credits)

翻译自: https://tympanus.net/codrops/2017/01/19/little-fragments-creating-a-simple-image-poster-effect/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值