Creating Infinite Horizontal Gradient Tween

Need Help Making an Infinite Scrolling Gradient (Like Anime Vanguards Header)

1. What do you want to achieve?
I’m trying to make a UIGradient move infinitely across an image (horizontally), similar to the animated header gradient in Anime Vanguards. I want it to loop smoothly so it looks endless.

2. What is the issue?
When I tween the gradient’s X offset infinitely using:

local info = TweenInfo.new(3.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, -1, false)

it works, but the problem is the first or last color of the gradient stays visible across the whole image when it loops, making it look static or not seamless.

I tried cloning my keypoints to have matching colors at negative and positive times (so it would wrap around seamlessly from -1 to 1 offset), but ColorSequenceKeypoint.new() doesn’t allow negative time values.

3. What solutions have you tried so far?

  • Tweening Offset.X from -1 to 1 with infinite repeat.
  • Duplicating keypoints at -time and time (not possible because negative times error).
  • Making sure the first and last colors match. still not seamless horizontally.
  • Looked through Creator Hub and DevForum posts but didn’t find a fix for this specific issue.

Extra details:
I’m trying to keep this simple and performance friendly. I don’t want to use any RenderStepped math or custom loops, just TweenService and UIGradient if possible. If anyone knows a simple method to make the horizontal gradient loop seamlessly, please let me know!

Gradient I’m using:

ColorSequence.new({
		ColorSequenceKeypoint.new(0, Color3.new(0.980392, 0.839216, 0.32549)),
		ColorSequenceKeypoint.new(0.104, Color3.new(1, 1, 0)),
		ColorSequenceKeypoint.new(0.353, Color3.new(1, 0.333333, 0)),
		ColorSequenceKeypoint.new(0.604, Color3.new(1, 1, 0)),
		ColorSequenceKeypoint.new(0.854, Color3.new(1, 0.333333, 0)),
		ColorSequenceKeypoint.new(1, Color3.new(0.980392, 0.839216, 0.32549)),
})

I have a script that’s pretty straight foward; pretty much you infintely loop the offset and when its out of bounds, reset it but on the other side and tween it back to the wanted side

local timeTakes = 1.5
local delayBetween = 2

while true do
	if script.Parent.Visible == true then
		gradient.Offset = Vector2.new(-1.5, 0)

		local tween = tweenService:Create(gradient, TweenInfo.new(timeTakes, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut), {Offset = Vector2.new(1.5, 0)})
		tween:Play()
		tween.Completed:Wait()

		task.wait(delayBetween)
	else
		break
	end
end

*note that you can change the TweenInfo to the one you have

This is just how to tween a gradient infinitely not the issue with the color “it works, but the problem is the first or last color of the gradient stays visible across the whole image when it loops, making it look static or not seamless.”

also u can do the code u sent just by

local info = TweenInfo.new(3.5, Enum.EasingStyle.Linear, Enum.EasingDirection.Out, -1, false)

if the 4th params is -1 then the tween will repeat infinitely and if the 5th param is false it will reset to the original value it was when the tween started each reset.

Something like this should work:

local function offsetColourSequence(originalColourSequence: ColorSequence, offset: number) -- original colour sequence, offset is a scalar (0-1)
	local keypoints = {}
	
	for _, keypoint in originalColourSequence.Keypoints do
		local newPoint = ColorSequenceKeypoint.new((keypoint.Time + offset) % 1, keypoint.Value)
		table.insert(keypoints, newPoint)
	end
	
	table.sort(keypoints, function(a, b)
		return a.Time < b.Time
	end)
	
	-- we find where along the point between last keypoint and first keypoint the end of the gradient is to find what colour we need for keypoint @ t=0 and t=1
	local keypoint1 = keypoints[1]
	local lastKeypoint = keypoints[#keypoints]
	
	local diff = 1 - lastKeypoint.Time
	local diff2 = keypoint1.Time
	local diffTotal = diff + diff2
	local t = diff2 / diffTotal -- this is the point where along the 2 points the end of the gradient is, we use this as the alpha for the ColourSequence where t=0 and t=1
	
	if keypoint1.Time ~= 0 then -- edge case where a keypoint at t=0 exists we don't need to wrap 
		table.insert(keypoints, 1, ColorSequenceKeypoint.new(0, keypoint1.Value:Lerp(lastKeypoint.Value, t)))
	end
	
	if lastKeypoint.Time ~= 1 then -- same as above for t=1
		table.insert(keypoints, ColorSequenceKeypoint.new(1, keypoint1.Value:Lerp(lastKeypoint.Value, t)))
	end
	
	return ColorSequence.new(keypoints)
end

Example usage would be something like:

local colourSequence = ColorSequence.new({ -- cache the original colour sequence, this is a rainbow for example
	ColorSequenceKeypoint.new(0, Color3.fromHSV(0, 1, 1));
	ColorSequenceKeypoint.new(1 / 6, Color3.fromHSV(1 / 6, 1, 1));
	ColorSequenceKeypoint.new(2 / 6, Color3.fromHSV(2 / 6, 1, 1));
	ColorSequenceKeypoint.new(3 / 6, Color3.fromHSV(3 / 6, 1, 1));
	ColorSequenceKeypoint.new(4 / 6, Color3.fromHSV(4 / 6, 1, 1));
	ColorSequenceKeypoint.new(5 / 6, Color3.fromHSV(5 / 6, 1, 1));
	ColorSequenceKeypoint.new(1, Color3.fromHSV(1, 1, 1))
})

local gradient = script.Parent:WaitForChild('Frame'):WaitForChild('UIGradient')

local offset = 0

while true do
	gradient.Color = offsetColourSequence(colourSequence, offset)
	offset = (task.wait() + offset) % 1
end
3 Likes

Works great tysm. Added it to my interface module.

local function OffsetColorSequence(OriginalColorSequence: ColorSequence, Offset: number): ColorSequence
	local Keypoints = {}

	for _, Keypoint in OriginalColorSequence.Keypoints do
		local NewPoint = ColorSequenceKeypoint.new((Keypoint.Time + Offset) % 1, Keypoint.Value)
		table.insert(Keypoints, NewPoint)
	end

	table.sort(Keypoints, function(A, B)
		return A.Time < B.Time
	end)

	local FirstKeypoint = Keypoints[1]
	local LastKeypoint = Keypoints[#Keypoints]

	local Diff = 1 - LastKeypoint.Time
	local Diff2 = FirstKeypoint.Time
	local DiffTotal = Diff + Diff2
	local T = Diff2 / DiffTotal

	if FirstKeypoint.Time ~= 0 then
		table.insert(Keypoints, 1, ColorSequenceKeypoint.new(0, FirstKeypoint.Value:Lerp(LastKeypoint.Value, T)))
	end

	if LastKeypoint.Time ~= 1 then
		table.insert(Keypoints, ColorSequenceKeypoint.new(1, FirstKeypoint.Value:Lerp(LastKeypoint.Value, T)))
	end

	return ColorSequence.new(Keypoints)
end

function Interface.StartHorizontalScroll(Gradient: UIGradient, ScrollTime: number)
	local ColorSeq = Gradient.Color
	local Offset = 0
	local Connection

	Connection = RunService.RenderStepped:Connect(function(Dt)
		Offset = (Offset + Dt / ScrollTime) % 1
		Gradient.Color = OffsetColorSequence(ColorSeq, Offset)
	end)

	return function()
		if Connection and Connection.Connected then
			Connection:Disconnect()
		end
		Connection = nil
	end
end
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.